I have created a simple Rails Engine to provide some general functionality(photo gallery) to an application. I want to be able to override the standard _header partial so that the menu for the gallery matches that of my main application. In my header view I call a helper that is part of application_helpers (main app), but I keep getting "undefined method" errors. From what I can tell the main app application_helpers are not being included (obviously) when I override the engines application layout or its partials.
So my question is, how do I override an engine view in the main application, and get access to the main application helper methods? I would still need access to the engine helpers as well as not to screw up the engine functionality.
Do I need to override the controllers as well? seem like a lot just to get some helpers.
Thanks
Rails 3.1.3
check out this blog post: http://www.candland.net/2012/04/17/rails-routes-used-in-an-isolated-engine/
The author adds a method_missing definition to the application helper in order to access the parent application's helpers.
/config/initializers/blogit.rb
module Blogit
module ApplicationHelper
def method_missing method, *args, &block
puts "LOOKING FOR ROUTES #{method}"
if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
if main_app.respond_to?(method)
main_app.send(method, *args)
else
super
end
else
super
end
end
def respond_to?(method)
if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
if main_app.respond_to?(method)
true
else
super
end
else
super
end
end
end
end
Try including the main app helper methods. For instance:
class MyEngineClass
include ApplicationHelper
#...
end
You may possibly need to require the file first, though I would expect Rails to correctly find it in this case.
Once ApplicationHelper is included, you should be able to directly use those helpers in the controller.
It also looks like you can call ClassName.helper("application") for a lot of Rails classes -- not sure if that will work here.
try creating a helper in your application with the same name of the helper in your engine in order to override engine helper methods.
I found this discussion particularly insightful. There are also some interesting ideas in the Rails Engine API docs under Isolated engine helpers.
Engines are supposed to be independent from the main app, that is why you can't access its helpers from the Engine.
However, there are hack-ish ways for giving your engine access to the helpers of the main app. This is how I did it:
# In the main app
# initializers/share_helpers_path_with_engine.rb
PhotoGallery::Engine.class_eval do
paths["app/helpers"] << File.join(File.dirname(__FILE__), '../..', 'app/helpers')
end
You need of course to change PhotoGallery to the actual name of your engine class.
Feel free to take a look at the Engines documentation (section about the paths): http://edgeapi.rubyonrails.org/classes/Rails/Engine.html
Disclaimer: I've only used this solution in Rails 3.2 engines.
If, in your engine you have a standard header partial vendor/gems/my_gallery_engine/app/views/application/_header.html.erb.
Then, override it in your main app by creating a customized partial app/views/application/_header.html.erb.
The override works because Rails' view template search path (by default) starts with the main apps' app/views directory, and then searches through engines' app/views in load order.
All of your main app's Helpers will be available in the partial.
Related
I am aware that I can override an applications view from within an engine by simply creating the same file within the engine and removing it from the application (eg: 'users/show.html.erb').
However, what I want is to be able to extend the applications view, not override.
Lets say I have a yield inside 'users/show.html.erb' of the main application:
yield :foo
What I want is for the engine to specify the same file 'users/show.html.erb' and to have a content_for block
content_for :foo {}
Thereby, injecting some template data from the engines view, into the applications view.
Obviously, the above won't work as once it has found the template file in the application, it won't look for one in the engine.
Is there a way to make this work?
There is no way to extend views that way in Rails. You can however, accomplish this by using partials. In your engine, write a partial view file users/_show.html.erb and then render it in your app's view:
# app/views/users/show
# will look for a partial called "_show.html.erb" in the engine's and app's view paths.
render partial: 'show'
It's just as easy as your suggestion.
This gem tries to implement partial extension of views, but it works similarly to what I just described: https://github.com/amatsuda/motorhead#partially-extending-views-in-the-main-app
I am trying to define some helper methods to be used in the app's controller, but it seems that rails don't even call the controller. just for the test I have the following controller in my app/controllers/my_engine/application_controller.rb and as the documents say rails should find it first and an error should raise because THIS_SHOULD_PRODUCE_ERROR is unknown, but the rspec happily executing without any errors!
class ApplicationController < ActionController::Base
THIS_SHOULD_PRODUCE_ERROR
end
I even tried to mimic the devise's way but the results are the same!
The guide section on the app directory suggests that the application_controller in an engine "will provide any common functionality for the controllers of the engine".
So I wouldn't expect that any additions to that controller will be available to all controllers in an application.
That also means that your application_controller is, I suspect, not getting called when you're running your test. Which would explain why you're not seeing an error.
In terms of how devise does it I think you need to be looking at how define_helpers works. The code you've linked to in your question is the application controller in the test app for the devise gem.
I noticed that I have got things wrong, and the application_controller in the engine does not get applied to application_controller in the app! Also, I couldn't figure out how the devise did it, but I have come up with the simple workaround for this:
require_relative 'controllers/helpers'
module Acu
module Injectors
class << self
ActiveSupport::Notifications.subscribe "start_processing.action_controller" do |**args|
eval((Acu::Configs.get :base_controller).to_s).class_eval do
include Acu::Controllers::Helpers
end
end
end
end
end
This will inject controller helpers to the user's base controller (which I get from the user, default: :ApplicationController) at the end of the class, which is perfect for me (but don't know how to add it to begging of the class if anyone needs it)
I recently came across a tricky situation related to rails view helpers.
The situation is like follows-
I am having a controller as Feature1::Feature1.1::Feature1.1.1Controller.
The Feature1.1 also includes other controllers like Feature1.1.2Controller, Feature1.1.3Controller...
So ofcourse related view helpers in folder app/helpers/feature1/feature1.1/...
Now the real problem I am facing is that a few helpers for feature1.1 includes the same method name method1 with related definition.
I was wondering how rails identifies all these helpers as I am noticing that the method1 i.e. being called in a view for the controller feature1.1.1 is using the definition of the method1 i.e. written for the controller feature1.1.2.
So does rails consider all helper modules defined in one folder as one?
In a view feature1/feature1.1/feature1.1.1/index I am making a method call for method1.
I am using rails3
It depends a little bit on your Rails version. With eralier Rails versions, Rails did only include application_helper.rb and <controler_name>_helper.rb.
Additional helper modules can be included via helper :helper_name1, :helper_name2, ... within your controller.
With later Rails verions (4.2.? and up, maybe previous versions too), Rails includes all helpers within your helper folder at once. You can set config.action_controller.include_all_helpers = false within application.rb and you will fall back to the old behaviour.
This makes the helper only available within your views. If you want to use a helper within your controller you still have to include your helper with include XXXHelper.
I did some research and would like to share some additional info.
As per #slowjack2k mentioned, view helpers are included by rails as a default behavior.
But my question was about the situation of same method names across multiple helpers.
I found this article to be useful in this scenario. Though it explains the behavior for Rails 4 but I found it behaves in the same fashion for Rails 3.2.2.
I will summarize the article -
If there will be any conflict in the same names of methods in different helper modules, rails will use method from latter file (alphabetically)
I'm writing a configurable Rails engine. I have an authentication_helper configuration option to define which helper should be called in a before_action in all controllers needing authentication.
The problem is that I don't have access to the parent app's helpers from the engine's controllers. My understanding is that this happens because the engine is isolated.
I have considered using a block instead of a method name, but I'm not sure if that would work, or if I would be able to cleanly access the authorization logic from outside my controllers.
Active Admin, which I have used in the past, has a similar configuration option. I have noticed that their engine is not isolated, so perhaps I'm overrating the importance of engine isolation?
Is there an elegant way to have the benefits of engine isolation while also allowing this kind of customization? Or should I just forego isolation altogether?
EDIT #1
Brad Werth pointed my in the right direction, as this works with a regular controller inheriting from ApplicationController::Base:
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
However, my engine's ApplicationController inherits from RocketPant::Base, which does not provide a helper method. I've tried to use a simple include (which works fine for regular controllers), but that doesn't work either (the controller can't find the helper).
Any ideas?
You can expose the implementing application's helpers available to the engine by including the following code in your engine.rb file:
engine.rb
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
The RailsAdmin Engine is also isolated, but they have the same configuration options as you would like to implement. They have configurable before_filters for both authentication and authorization. Have a look at this.
As far as I can tell, they just subclass the parent controller like this::ApplicationController or instead you can configure one (ref).
For your controller you could just create your own EngineController, that inherits from RocketPant::Base and maybe just create a method there that calls the configured authentication method directly via send on the parent controller.
As the RocketPant::Base Class does not inherit from ApplicationController::Base I guess you have to find some custom way around this and can't go the normal ways for Rails Engines. Maybe you could also try to file an issue to the rocket_pant repo, to add the helper method. As far as I read they soon want to inherit from ApplicationController::Base anyway in 2.0 (ref).
I think you impulse on keeping the isolation is good - because a solution that relies on a method 'just being there' is a pain to debug if s.th. goes wrong in the app using your gem (i.e. typo in method name).
Further if your gem evolves, an some day it won't need the method, in an explicit way it's very convient to give a meainingful error:
You could provide a config method to be called in an initializer:
YourGem.configure do |config|
config.add_callback { MyApp.doIt() }
end
I found this discussion particularly insightful. There are also some interesting ideas in the Rails Engine API under Isolated engine helpers.
The Rails Engine API docs helped me figure out a good solution for url_helpers
You probably moved on by now, but if anyone else needs access to the "parent app" application helpers. You could always just include it explicitly in the application controller in your engine. like so:
include Rails.application.helpers
What I've run into is this:
AlchemyCMS is a Rails Engine for allowing Rails applications to have a Content Management System. It also has a preview page where it can load up an iframe of the example page with the layout. The layout here is the Spree layout. I've modified Alchemy to be able to load up the spree application layout and not its default.
In doing so, it is not loading up the helper methods. I am currently receiving:
undefined local variable or method `title' for #<#<Class:0x007f8dcc359498>:0x007f8de17dd6a8>
Where title is the first helper method in the application.
I've tried 5000 different techniques to try to load in Spree's helper methods into AlchemyCMS and I just can't do it.
Does anyone know how?
Ben,
You could do so by either including Spree's helpers within your application controller or within the base Alchemy controllers.
There is an extension for alchemy and spree together, which does a similar thing here:
https://github.com/magiclabs/alchemy_spree/blob/master/app/controllers/spree/base_controller_decorator.rb
You will just want to go in the opposite direction so instead of decorating a Spree controller to add Alchemy in you would decorate Alchemy controllers to include whichever of Spree's controller helpers you need to use:
https://github.com/spree/spree/blob/master/core/app/controllers/spree/base_controller.rb
In this case you need to include the common controller helpers:
https://github.com/spree/spree/blob/master/core/lib/spree/core/controller_helpers/common.rb
EDIT:
Alchemy::BaseController.class_eval do
include Spree::Core::ControllerHelpers
include Spree::Core::ControllerHelpers::Store
helper Spree::Core::Engine.helpers
end