Rails mountable engines helper methods not found when routing from main app

1.7k views Asked by At

I have a module as a mountable engine mounted to a main app through

mount MyEngine::Engine, :at => '/myengine'

I have everything namespaced in the negine and the engine has it's own views in engine/app/views/myengine/

Everything works fine when I run rails server then try to access

localhost:3000/myengine

first then go to the root of the main app and come back to the engine through a link in the index view of the main app

However when I start the server, go to localhost:3000 and from there click on the link to the engine module it tries to fetch the views correctly however the methods contained in the engine's helpers raises an error upon call for them being undefined.

I am on rails 4

3

There are 3 answers

0
Ahmadposten On BEST ANSWER

I used eager_load to load the mountable engine in it's initialized in the main app and everything seems to be working now.

Myengine::Engine.eager_load!

3
Yury Lebedev On

You need to include the helpers explicitely in your lib/engine.rb file:

initializer 'your-gem-name.setup_helpers' do |app|
    app.config.to_prepare do
      ActionController::Base.send :include, HelperModuleName
    end
  end
end
0
Cameron On

I just ran into this issue as well. I had defined an ApplicationController inside my engine and was seeing NoMethodErrors when I tried to use helper methods inside one of my engine's controllers.

The Problem

The code looked something like this:

in my_engine/app/controllers/my_engine/application_controller.rb

module MyEngine
  class ApplicationController < ActionController::Base
    helper ApplicationHelper
  end
end

in my_engine/app/controllers/my_engine/projects_controller.rb

module MyEngine
  class ProjectsController < ApplicationController
    def new
      # some action code here
    end
  end
end

in my_engine/app/helpers/my_engine/application_helper.rb

module MyEngine
  module ApplicationHelper
    def translations_include_tag
      javascript_include_tag "translations-#{I18n.locale}.js"
    end
  end
end

If I navigated to a route in the host rails app, then clicked a link that navigated to my_engine/projects/new, I would get a NoMethodError saying translations_include_tag didn't exist.

The Explanation

I think this was happening because the application had two classes with the same name: ApplicationController in MyEngine and ApplicationController in the host app. When the first route outside the engine is visited, Rails autoloads the ApplicationController from the host app. What that means is Rails associates the main app's ApplicationController with the name "ApplicationController". When you navigate to my_engine/projects/new, Rails lazy loads ProjectsController which also inherits from ApplicationController. Rails/ruby thinks you're referring to the same ApplicationController it has already loaded, so in effect ProjectsController ends up inheriting from the host app's ApplicationController instead of the one in the engine. Since the translations_include_tag method isn't defined in the host app's ApplicationController, ruby raises a NoMethodError when the view tries to call it.

The Solution

I was able to fix the problem by explicitly inheriting from MyEngine's ApplicationController:

in my_engine/app/controllers/my_engine/projects_controller.rb

module MyEngine
  # changed from just plain 'ol ApplicationController
  class ProjectsController < ::MyEngine::ApplicationController
    def new
      # some action code here
    end
  end
end

After fixing the inheritance issue, the NoMethodError went away.

The accepted answer says to use eager loading, which will also fix the problem because it forces the engine's ApplicationController to load first. Since the engine's ApplicationController is namespaced inside MyEngine, it's full name is MyEngine::ApplicationController, which doesn't conflict with the main app's ApplicationController because the constant names are different.