为什么当 cache_classes = false 时,Rails 引擎初始化程序中的包含会出现故障?
我有一个引擎,它在其初始化程序中扩展另一个引擎的类,如下所示:
module MyApp
class Engine < ::Rails::Engine
initializer 'extend Product' do
AnotherApp::Product.send :include, MyApp::ProductExtender
end
end
end
ProductExtender
模块在包含 AnotherApp::Product 时调用其上的一些方法,例如,
module ProductExtender
def self.included( model )
model.send :include, MethodsToCall
end
module MethodsToCall
def self.included( m )
m.has_many :variations
end
end
end
这适用于测试和生产环境,但是当 config.cache_classes = false
时,当我尝试调用 ProductExtender 定义的内容(例如 @product.variations)时,它会向我抛出 NoMethodError
。
不用说,看到我所有的测试都通过了,然后却因为开发中的错误而受到重创,真是令人不寒而栗。当我设置 cache_classes = true
时,这种情况不会发生,但这让我想知道我是否在做一些不应该做的事情。
我的问题是双重的:为什么会发生这种情况,是否有更好的方法我应该实现在另一个应用程序的对象上扩展/调用方法的功能?
谢谢大家!
I have an Engine which is extending another Engine's classes in its initializers like so:
module MyApp
class Engine < ::Rails::Engine
initializer 'extend Product' do
AnotherApp::Product.send :include, MyApp::ProductExtender
end
end
end
The ProductExtender
module calls some methods on the AnotherApp::Product when it is included, e.g.
module ProductExtender
def self.included( model )
model.send :include, MethodsToCall
end
module MethodsToCall
def self.included( m )
m.has_many :variations
end
end
end
This works in test and production environments, but when config.cache_classes = false
, it throws a NoMethodError
at me when I try to call something defined by the ProductExtender, like @product.variations.
Needless to say, it is chilling to see all my tests pass and then get slammed with an error in development. It doesn't happen when I set cache_classes = true
, but it makes me wonder if I'm doing something I shouldn't be.
My question is twofold: Why is this happening, and is there a better way I should be achieving this functionality of extending/calling methods on another application's object?
Thanks all!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我设法使用
to_prepare
块而不是初始化器解决了这个问题。to_prepare
块在生产中和开发中的每个请求之前执行一次,因此似乎满足我们的需求。当我研究
Rails::Engine
时,这一点并不明显,因为它继承自Rails::Railtie::Configuration
。因此,我不会使用问题中的代码,而是:
I managed to solve this problem using a
to_prepare
block instead of the initializer. Theto_prepare
block executes once in production and before each request in development, so seems to meet our needs.It wasn't obvious when I was researching
Rails::Engine
since it is inherited fromRails::Railtie::Configuration
.So instead of the code in the question, I would have:
cache_classes 实际上有一个误导性的名称:不涉及缓存。如果将此选项设置为 false,rails 会显式卸载您的应用程序代码并在需要时重新加载。这使得您在开发过程中所做的更改无需重新启动(服务器)进程即可生效。
在您的情况下,AnotherApp::Product 会被重新加载,ProductExtender 也是如此,但初始化程序在重新加载后不会再次触发,因此 AnotherApp::Product 不会“扩展”。
我非常了解这个问题,并最终使用 cache_classes = true 运行我的开发环境,并偶尔重新启动我的服务器。我在引擎/插件上没有太多开发要做,所以这是最简单的方法。
cache_classes has actually a misleading name: There is no caching involved. If you set this option to false, rails explicitly unloads your application code and reloads it when needed. This enables for changes you make in development to have an effect without having to restart the (server) process.
In your case, AnotherApp::Product is reloaded, as is ProductExtender, but the initializer is not fired again, after the reload, so AnotherApp::Product is not 'extended'.
I know this problem very well and ended up running my development environment with cache_classes = true and occasionally restart my server. I had not so much development to do on engines/plugins, so this was the easiest way.