Rails3 引擎助手覆盖
所以我有一个 Rails 3.0 引擎(gem)。
它在 app/controllers/advanced_controller.rb 处提供了一个控制器,在 app/helpers/advanced_helper.rb 处提供了一个相应的帮助器。 (当然还有一些观点)。
到目前为止一切顺利,控制器/帮助器/视图在使用 gem 的应用程序中自动可用,太棒了。
但我想让本地应用程序选择性地覆盖引擎中 AdvancedHelper 的辅助方法(并且最好能够调用“超级”)。这是一个非常合理的事情,想要允许,对吧,一个完全合理的(我认为常见的)设计?
问题是,我似乎找不到任何方法让它发挥作用。如果应用程序定义了它自己的 app/helpers/advanced_helper.rb (AdvancedHelper),那么来自引擎的那个根本不会被加载——所以如果你想替换其中的所有帮助器方法(不调用 super),那么这将是可行的,但如果你只是想覆盖一个则不然。
所以这实际上是有道理的,所以我选择了一个不同的名字。我们将本地的命名为 ./app/helpers/local_advanced_helper.rb (LocalAdvancedHelper)。这个助手确实被加载了,如果我在其中放置了原始引擎的 AdvancedHelper 中没有的方法,它就可供视图使用。
但是,如果我在那里放置一个与引擎的 AdvancedHelper 中的方法同名的方法......我本地的方法永远不会被调用。就像 AdvancedHelper(来自引擎)在调用链中比 LocalAdvancedHelper(来自应用程序)更早。事实上,如果我打开调试器,并查看 helpers.ancestors,这正是发生的情况,它们在祖先链中的顺序与我想要的相反。因此,AdvancedHelper(来自引擎)理论上可以调用“super”来调用 LocalAdvancedHelper(来自应用程序)——但这当然没有多大意义,你永远不想这样做。
但我想做想做的事...我做不到。
任何人都有任何想法,有什么方法可以提供这种设计,这对我来说似乎完全合理,应用程序可以有选择地覆盖引擎中的辅助方法?
任何人都可以解释为什么它会这样工作吗?我尝试查看实际的 Rails 源代码,但很快就迷失了方向,围绕这些东西的代码非常抽象,分散在很多地方。
这是一个非常深奥的问题,我悲观任何人都会有任何想法,我希望你给我惊喜!
== 更新
好的,为了了解 Rails 代码的哪一部分被调用,我在每个助手上放置了一个“def self.included ; debugger ; end”,然后在调试器中我可以引发异常以查看堆栈跟踪。
这仍然不能真正帮助我弄清问题的根源,Rails 代码到处乱跳,而且非常令人困惑。
但很明显,具有“标准”名称的助手(即 WidgetController 的 WidgetHelper)由不同的 Rails 代码调用,以包含在给定控制器的“主”视图助手模块中,而不是其他助手。我想知道是否给助手一个不同的名称,然后使用(“helper OtherNamedAdvancedHelper”)手动将其包含在我的控制器中,如果这会改变加载顺序。
So I have a Rails 3.0 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 seem to find any way to make it work. If the application defines it's own app/helpers/advanced_helper.rb (AdvancedHelper), then the one from the engine never gets loaded at all -- so that would work if you wanted to replace ALL the helper methods in there (without calling super), but not if you just want to over-ride one.
So that kind of makes sense actually, so I pick a different name. Let's call my local one ./app/helpers/local_advanced_helper.rb (LocalAdvancedHelper). This helper DOES get loaded, if I put a method in there that wasn't in the original engine's AdvancedHelper, it is available to views.
But if I put a method in there with the same name as one in the engine's AdvancedHelper... my local one NEVER gets called. It's like the AdvancedHelper (from engine) is earlier in the call chain than the LocalAdvancedHelper (from app). Indeed, if I turn on the debugger, and look at helpers.ancestors, that's exactly what's going on, they're in the reverse order I'd want in the ancestor chain. So AdvancedHelper (from engine) could theoretically call 'super' to call up to LocalAdvancedHelper (from app) -- but that of course wouldn't make a lot of sense to do, you'd never want to do that.
But what I would want to do... I can't do.
Anyone have any ideas, is there any way to provide this design, which seems perfectly reasonable to me, where an app can selectively over-ride a helper method from an Engine?
Anyone have any explanation of why it's working the way it is? I tried looking at actual Rails source code, but got lost pretty quick, the code around this stuff is awfully abstract split amongst a bunch of places.
This is pretty esoteric question, I'm pessimistic anyone will have any ideas, I hope you surprise me!
== Update
Okay, in order to understand what part of Rails code is being called where, I put a "def self.included ; debugger ; end" on each of my helpers, then in the debugger I can raise an exception to see a stack trace.
That still isnt' really helping me get to the bottom of it, the Rails code jumps all over the place and is pretty confusing.
But it's clear that a helper with the 'standard' name (ie WidgetHelper for WidgetController) is called by different rails code, to include in the 'master' view helper module for a given controller, than other helpers are. I'm wondering if I give the helper a different name, then manually include it in my controller with ("helper OtherNamedAdvancedHelper"), if that will change the load order.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(3)
感谢您的详细阐述。我认为这确实是一个问题。而且它仍然存在于 Rails 3.2.3 中,因此我提交了一个问题。
我想出的最难闻的解决方法是做一个“半别名方法链”:
module MountedEngineHelper
def advanced_helper
...
end
end
module MyHelper
def advanced_helper_with_extra_behavior
advanced_helper
extra_behavior
end
end
明显的缺点是你必须更改你的模板以便调用你的助手。至少,你在那里明确了额外行为的存在。
Rails4 的这些发行说明似乎与这个问题密切相关,并且可能注意到它已被修复:
http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#helpers-loading-order
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
我们可以使用 Module#class_eval 覆盖。
在主应用程序中,
这样引擎助手中定义的其他方法仍然可用。
We can use Module#class_eval to override.
In main app,
In this way other methods defined in engine helper are still available.