The polymorphs plugin dynamically injects methods into child models. This means that if you referenced a child model before the parent was loaded, the methods would be missing.
inversion of control or what have you
I solved the problem by adding a dependency injection mechanism. Here’s the entire code:
module Dependencies
mattr_accessor :injection_graph
self.injection_graph = Hash.new([])
def inject_dependency(target, *requirements)
target, requirements = target.to_s, requirements.map(&:to_s)
injection_graph[target] =
((injection_graph[target] + requirements).uniq - [target])
requirements.each {|requirement| mark_for_unload requirement }
end
def new_constants_in_with_injection(*descs, &block)
returning(new_constants_in_without_injection(*descs, &block)) do |found|
found.each do |constant|
injection_graph[constant].each {|req| req.constantize}
end
end
end
alias_method_chain :new_constants_in, :injection
end
explanation, usage
See what it does? Imagine that the Tag and Tagging classes modify the Recipe class (by injecting a new method, or relationship, or something). Normally in development mode if Recipe gets reloaded, the injections will get lost, since Rails doesn’t know it has to re-evaluate Tag and Tagging after Recipes is refreshed. But now we can do as follows:
Dependencies.inject_dependency("Recipe", "Tag", "Tagging")
This way, when the Recipe constant gets refreshed, Tag and Tagging will also get refreshed, and can go patch up Recipe again. Because constantize()
doesn’t reload the same constant multiple times, there is no danger of infinite cycles.
remember, only for development mode
This is not at all useful in production mode, since classes aren’t reloaded. But in development mode it can make a big difference in sanity.
If there is interest I can release it as a separate plugin.
postscript: on the plugin boot process
Also in version 27.1, there is some method chaining to let the plugin finish booting itself after the config.after_initialize
block runs. This is useful because users are supposed to set plugin start-up options in config.after_initialize
(see here).
The startup sequence is like so (compressed from railties/lib/initializer.rb
):
def process
load_environment # environment.rb
load_plugins # init.rb for your plugin
load_observers
initialize_routing
after_initialize # where your user configures your plugin
end
What if things in init.rb
need to know the configuration options? Check lib/has_many_polymorphs/autoload.rb
for an example of a fix.
Unfortunately config.after_initialize
doesn’t allow multiple blocks the way Dispatcher.to_prepare
does. There is a Rails patch waiting to happen here…