Solved: plugin models unloading after first request


#1

I haven’t been able to find this advice anywhere else, so I am posting
here so that it may help another lost soul.

Some plugins have a problem: if a plugin applies a mixin directly to a
model in app/models, this mixin gets unloaded by rails after the first
request. This only happens in development mode. The symptom of this is
an application that works for the first request but fails on subsequent
requests.

A call to Dispatcher.to_prepare will get around this problem by
re-applying any mixin that modifies the core models on each request. In
production mode, the classes are not unloaded and so the mixin is only
applied once.

“Normal” plugins don’t have this problem: they modify active record, and
then the core models call these extensions. When these core models are
reloaded by rails, the plugin code is then reloaded as well. The problem
only shows up when you want to modify a model in apps/model without
requiring any code change to that model.

This style of using plugins is not considered to be a good idea by many.
However, I am writing plugins that are designed exclusively for a
particular application. These plugins enable optional features of a
specific application–they do not try to modify the behavior of rails or
extend active record.

Here is the code:

(1) modify Engines:Plugin

require ‘dispatcher’

Engines::Plugin.class_eval do
def apply_mixin_to_model(model_class, mixin_module)
Dispatcher.to_prepare {
model_class = Kernel.const_get(model_class.to_s) # \ weird, yet
mixin_module = Kernel.const_get(mixin_module.to_s) # / required.
model_class.send(:extend, mixin_module.const_get(“ClassMethods”))
model_class.send(:include,
mixin_module.const_get(“InstanceMethods”))
model_class.instance_eval &(mixin_module.class_definition())
}
end
end

(2) in your plugin’s init.rb:

in this case, there is a model app/models/language.rb, and module

vendor/plugins/myplugin/app/models/language_extension.rb

apply_mixin_to_model(Language, LanguageExtension)

(3) this is what my language_extension.rb looks like:

module LanguageExtension
module ClassMethods
end

module InstanceMethods
def percent_complete()
count = Key.count_all
if count > 0
(Key.translated(self).count / count * 100.0).round.to_s + ‘%’
end
end
end

def self.class_definition
lambda {
has_many :translations, :dependent => :destroy
}
end
end


Most the code in apply_mixin_to_model is not really required. But it
seemed to me a little repetitive to type this over and over again for
each mixin:

module MyModule
def self.included(base)
base.extend(ClassMethods)
base.instance_eval do
include InstanceMethods
has_many :somethings

end
end

end

So, most the code in apply_mixin_to_model is just doing that repeated
stuff for you, so long as the submodules are named right (ie
InstanceMethods, etc).

The simple thing to do is just put:

Dispatcher.to_prepare {
Language.send(:include, LanguageExtension)
}

In the plugin’s init.rb. But again, I am trying to get fancy, perhaps at
my own peril.

Hope that helps someone,
-elijah


#2

I cannot thank you enough for posting this. Have you found a more
elegant way to handle this?