Why do includes in Rails Engine initializers malfunction when cache_classes = false?
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 Ano开发者_JAVA技巧therApp::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!
I managed to solve this problem using a to_prepare
block instead of the initializer. The to_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 from Rails::Railtie::Configuration
.
So instead of the code in the question, I would have:
module MyApp
class Engine < ::Rails::Engine
config.to_prepare do
AnotherApp::Product.send :include, MyApp::ProductExtender
end
end
end
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.
精彩评论