rails engine view helpers load order

2,657 views
Skip to first unread message

Jonathan Rochkind

unread,
Jun 17, 2011, 5:00:45 PM6/17/11
to Ruby on Rails: Core
This one is driving me crazy, appreciate it VERY much if someone can
even give me some hints at where to look in Rails source code to
understand/debug what's going on, I'm getting lost trying to look
through it.

Rails 3.0.8. I have an engine(gem).

It provides a controller at app/controllers/advanced_controller.rb,
and a corresonding helper at app/helpers/advanced_helper.rb. (And some
views of course).

So far so good, the controller/helper/views are just automatically
available in the application using the gem, great.

But I want to let the local application selective over-ride helper
methods from AdvancedHelper in the engine (and ideally be able to call
'super'). That's a pretty reasonable thing to want to allow, right, a
perfectly reasonable (and I'd think common) design?




Problem is I can't get it to work. Let's say there's a method
#do_something in the engine's app/helpers/advanced_helper.rb.

* If the local app provides an app/helpers/advanced_helper.rb,
then it completely replaces the one from the engine, the one from the
engine isn't loaded at all. (So it has none of it's methods, even
though we just wanted to over-ride one of em). Okay, this isn't
actually TOO unexpected.

* So I provide a helper called, say
local_advanced_helper.rb(LocalAdvancedHelper) in my local app/helpers.
It DOES load. If it implements a #new_method_name, that helper is of
course available in views (including the engine's views, as it
happens). However, if it tries to over-ride the engine's
#do_something ... the local do_something is never called.

The engine's helper seems to be 'included' in the module providing
helper methods to views earlier in the call chain (later in the
'include' order) then my local helpers. So there's no way for local
helpers to over-ride helpers from the engine. (The engine could
theoretically call 'super' to call 'up' to the local view helper with
the same name, but of course that makes little sense, that kind of
dependency is probably seldom appropriate). The ones from the engine
are always first in the call chain, before any view helper modules in
local app.





Can anyone shed any light on what's going on? Including pointing me to
the relevant parts of Rails code? Or suggesting any way I can get
this kind of design (local app can over-ride view helpers provided by
Engine) to work? Or tell me if this is a bug, or by design, or
neither (just didn't consider use case), or what?

Any feedback much appreciated. I've been going crazy trying to figure
this out for hours now. Also posted (in slightly different words) at
http://stackoverflow.com/questions/6380064/rails3-engine-helper-over-ride

Jonathan

Ryan Bigg

unread,
Jun 18, 2011, 7:47:29 PM6/18/11
to rubyonra...@googlegroups.com
I would have this helper module inside a namespace within the engine, which will allow people to have a similarly named one in the application. In the application's helper, then I would just include the engine's one, the process of which would make those methods available in the application without overriding the engine. After that, it's just a matter of overriding the methods underneath the `include` statement.

I don't claim that this is the canonical way to do it, but it is a clean way. Maybe other people have thoughts as well on this.
--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To post to this group, send email to rubyonra...@googlegroups.com.
To unsubscribe from this group, send email to rubyonrails-co...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.

Everton Moreth

unread,
Jun 19, 2011, 12:38:21 PM6/19/11
to rubyonra...@googlegroups.com
Your application will lazy load your ApplicationHelper. Wich means that when you call your engine Helper, the app will look through all folders until it finds one named AdvancedHelper.
When you create your own AdvancedHelper it will find it on the app and then never lookup at the engine. So, nothing gets loaded (consequently nothing gets overrided) from your gem.

You could inherit from the engine Helper or even include it as Ryan said. But you'll need to namespace it.
Or you could force the engine load on initialization of the app, and then reopen the class on the app. But I would go for including it.

Everton

Jonathan Rochkind

unread,
Jun 20, 2011, 12:09:48 PM6/20/11
to Ruby on Rails: Core
Thanks for the replies. So I understand why I can't create a class
with the exact same name in local app -- if I do it will entirely
replace the one in the plugin/gem. I understnad that.

What I don't understand is why I can't manage to selectively over-ride
view helpers from the plugin in the local app. Why the plugin's
helper gets loaded such that it's first in the call chain, and any
application view helper modules are further 'up' in the call chain, so
can't over-ride it.

Scrambling through the Rails code trying to figure it out, it is very
confusing code, jumping all over the place, to load helpers. (put a
"def self.included ; debugger ; end " in my various helper modules, in
which i can raise an exception in the debugger to see a back trace, in
order to try to begin understanding what Rails is doing).

But it looks like helpers with the 'default' name (WidgetHelper for
WidgetController) get loaded through a somewhat different path through
rails, and that could be part of it.

Perhaps if I give my helper a different name, and then manually do a
"helper OtherNameWidgetHelper" in the controller (defined in the gem),
that would allow the applications' helpers to over-ride it. I may try
that.

I am not happy with solutions that _require_ something to be generated
into the local app for default behavior. The nice thing about the
current design (if it worked), is that if you _don't_ want to over-
ride a helper from the gem, you don't need anything in the local app,
not a stub file or an 'include' in your ApplicationHelper etc. You
only need to do something if you DO want to over-ride. (In this case,
wanting to over-ride will be a rare thing).

I also not happy with solutions that require the local app to add
something to ApplicationHelper -- because it makes the helper
available for ALL controllers, not just the one it belongs to. Rails
seems to keep switching back and forth on whether "helper :all" is a
default, or is even mandatory behavior you can't change -- I'm not
sure what Rails 3.0.8 or Rails 3.1 are doing here, but it gets
confusing, keeps switching, and I don't want to assume this behavior,
or require the app to use this behavior in a current or future version
of Rails that may not otherwise require it. That is, I don't want to
force the user to include the gem-supplied helper module in _every_
controller (via ApplicationHelper), just in order to make it over-
rideable.

Hey Ryan, you say many other people have thoughts on this -- I've been
googling like crazy to find em, but haven't found much that is clearly
relevant to Rails 3.0 final and beyond -- if you have any URLs to good
places to see other ideas relevant in current rails, please do share!

Jonathan
> >http://stackoverflow.com/questions/6380064/rails3-engine-helper-over-...

Jonathan Rochkind

unread,
Jun 20, 2011, 12:20:25 PM6/20/11
to Ruby on Rails: Core
Sadly my idea to use a non-default name for the helper did not work.

What I'd really like:
1. A controller can be loaded from the plugin-gem. It can supply it's
own helper methods, from a view helper module in the plugin gem.
2. No stub code needs to be generated into the app to make this work.
(So far so good, it DOES work that way).
3. The local app, however, CAN implement code to over-ride these
helper methods, with a call to super.

This does not seem to be possible. #3 is not possible with #2; there
are designs that make #3 possible, but only by generating stub code
into the local app for _all_ cases, not just cases where a local over-
ride is desired. Which is frustrating because if you don't care about
supporting the over-riding case, you don't need a design with stub
code generated into local app.

This seems so obviously a good design to me that Rails should support,
and also seems feasible to support -- you just need to make sure that
local application defined helpers are 'include'd into the master view
helper module in the right order to be _earlier_ in the call chain
then any helpers from plugins. So I kind of want to submit a patch
to make it so -- but again, the Rails logic for loading helpers ends
up being so byzantine that I'm having trouble following it to figure
if it's feasible in the given actual codebase, and if so how. If
anyone has any tips for understanding what Rails methods are doing
what with regard to loading helpers from a gem-plugin and a local
app, it would be much appreciated.

Rodrigo Rosenfeld Rosas

unread,
Jun 20, 2011, 12:26:27 PM6/20/11
to rubyonra...@googlegroups.com
From an API perspective, maybe what you want is something event-based,
like Redmine or Chiliproject's plugins system.

Take a look at Apotomo (http://apotomo.de/) for an example of what I'm
talking about. Maybe that approach makes more sense than trying to
emulate some OO inheritance overriding methods in Modules through class
reopening and wanting to use "super" that way. Ruby doesn't support
that and I would recommend you to try another approach if your desired
results are something like Redmine's plugin system.

Cheers, Rodrigo.

Jonathan Rochkind

unread,
Jun 20, 2011, 1:00:39 PM6/20/11
to Ruby on Rails: Core
Thanks, I'll take a look at Redmine.

ruby totally DOES support this though, and it often works. I'm not
sure what you're suggesting ruby doesn't support?

Rails totally adds all your helper modules into a master template
helper module using ruby 'include', it already does that, I wasn't
suggesting adding that design, that's the design that's already
there.

And ruby certainly does support having multiple modules 'included'
into a given Module or Class, such that when the same method name
exists in both modules, the latter include'd one takes precedence, and
can still call 'super' to call up the chain. Ruby totally supports
that, it's a core part of the ruby language. And Rails is indeed
including multiple modules into one base Module in order to supply
multiple view helper modules, it's already doing that. (as far as I
can tell, looking through the source code and using a debugger).

Rails (rather than ruby) does not seem to currently support the
particular use case under question here, but it's not clear to me why
not or why it couldn't. It's just a question of what order the
various helper modules get 'include'd into that base module. But the
Rails code is definitely confusing here, I can't quite figure out
what's going on exactly to determine the order of include'ing.

Jonathan

On Jun 20, 12:26 pm, Rodrigo Rosenfeld Rosas <rr.ro...@gmail.com>
wrote:
> >http://stackoverflow.com/questions/6380064/rails3-engine-helper-over-...
>
> > Jonathan

Rodrigo Rosenfeld Rosas

unread,
Jun 20, 2011, 1:11:52 PM6/20/11
to rubyonra...@googlegroups.com
Em 20-06-2011 14:00, Jonathan Rochkind escreveu:
> Thanks, I'll take a look at Redmine.

This was just an example. Redmine (nor Chiliproject) is not ported to
Rails 3 yet, so I listed it just to exemplify the kind of solution
you're looking for. I guess you won't be able to get some idea from its
source code...

> ruby totally DOES support this though, and it often works. I'm not
> sure what you're suggesting ruby doesn't support?

I'm talking about this:

module A
def some_method
1
end
end

module A # module reopening
def some_method
super * 2 # will raise an exception
end
end

This is different from

module SomeNamespacing
class A
def some_method
1
end
end
end

class A < SomeNamespacing::A
def some_method
super * 2
end
end

> Rails totally adds all your helper modules into a master template
> helper module using ruby 'include', it already does that, I wasn't
> suggesting adding that design, that's the design that's already
> there.

But Rails doesn't change Ruby behavior: it is just composition working
here...

> And ruby certainly does support having multiple modules 'included'
> into a given Module or Class, such that when the same method name
> exists in both modules, the latter include'd one takes precedence, and

> can still call 'super' to call up the chain...

Here, I may be wrong, but although I agree that the latest one will be
called, I don't think super will call the overridden method in the chain.

Cheers, Rodrigo.

Everton Moreth

unread,
Jun 21, 2011, 2:23:27 PM6/21/11
to rubyonra...@googlegroups.com
If I got what you said about the inverse call chain, I would search for a wrong order in the paths collection.

The application path will be first in the load order... Maybe prepending your gem path into that (instead of appending) can solve it.

Everton

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To post to this group, send email to rubyonrails-core@googlegroups.com.
To unsubscribe from this group, send email to rubyonrails-core+unsubscribe@googlegroups.com.

Nick Sutterer

unread,
Jun 21, 2011, 7:40:55 AM6/21/11
to Ruby on Rails: Core
This really sounds like you're better off with Cells (Portlets for
Rails) http://cells.rubyforge.org/ - we try hard to encourage
encapsulation and putting view components into separate engines.

Nick
> >http://stackoverflow.com/questions/6380064/rails3-engine-helper-over-...
>
> > Jonathan
Reply all
Reply to author
Forward
0 new messages