Plugin Conventions Redux
Posted by trevor Mon, 31 Oct 2005 18:59:00 GMT
Last week Jamis responded to my post about plugin naming conventions.
If any of the rest of this post is going to make sense you’d better check out the above links first.
Anyhow, there’s been a bit more mailing-list to-ing and fro-ing about the fact that Jamis’ suggestion has problems, which I’ll write about today.
When is this an issue?
This is something that we should get out of the way here and now.
If your plugin is just mixing-in methods for a DSL (excellent link, by the way) like a new ActiveRecord ‘Acts’ – or you have some other situation where you only have have a couple of files to ‘require’, it’s a lot simpler and clearer to just require
those files explicitly in your init.rb
. For example:
plugins/enumerations_mixin/init.rb
plugins/enumerations_mixin/lib/proto_cool/enumerations/acts_enumerated.rb
plugins/enumerations_mixin/lib/proto_cool/enumerations/has_enumerated.rb
plugins/enumerations_mixin/lib/proto_cool/enumerations/virtual_enumerations.rb
My init.rb
file will look like this:
require 'proto_cool/enumerations/acts_enumerated'
require 'proto_cool/enumerations/has_enumerated'
require 'proto_cool/enumerations/virtual_enumerations'
ActiveRecord::Base.class_eval do
include ProtoCool::Enumerations::ActsEnumerated
include ProtoCool::Enumerations::HasEnumerated
end
Complex Hierarchies
But what if your plugin has a lot of files to require? You may be tempted to organize your require
s as Jamis suggested in his email. After all, it looks a lot like how rails organizes its own require
calls. A stripped down example of a complex hierarchy might be:
lib/jamis
lib/jamis.rb
lib/jamis/buck
lib/jamis/buck.rb
lib/jamis/buck/my_module.rb
lib/jamis/buck/my_other_module.rb
lib/jamis.rb
would contain:
require 'jamis/buck'
and lib/jamis/buck.rb
would contain:
require 'jamis/buck/my_module'
require 'jamis/buck/my_other_module'
The idea is that you could simply refer to Jamis::Buck::MyModule
or Jamis::Buck::MyOtherModule
without having to do any explicit require
calls in the code that first references those modules. Rails will automagically “require 'jamis'
” the first time you reference the Jamis
module.
The problem with this approach is that once “require 'jamis'
” has been called, all subsequent calls will have no effect, even if there are many jamis.rb
files in the $LOAD_PATH
. If Jamis releases another plugin that relies on automagic require
s his full set of classes won’t be loaded.
Strategies
There are a number of strategies you can use to work around this. First of all, you can just use explicit requires in your init.rb
file. You can still use Jamis’ method for everything in your module namespace that is unique to the current plugin though. Using the above example you’d just get rid of jamis.rb
and in your init.rb
file you’d have a single “require 'jamis/buck'
”.
Another strategy would be to use load
instead of require
for all files that you (as a plugin author) know will appear in many places in the $LOAD_PATH
. You could keep your jamis.rb
file but in your init.rb
you would simply say “load 'jamis.rb'
”.
Pie in the Sky
There’s something slightly non-intuitive about the above strategies though. I had one of those “wouldn’t it be nice if…” moments and knocked together some code that allows this:
inlib/jamis.rb
module Jamis
include Dependencies::AutoRequire
auto_require 'buck'
end
in lib/jamis/buck.rb
module Jamis::Buck
include Dependencies::AutoRequire
auto_require 'my_module'
auto_require 'my_other_module'
end
in init.rb
load 'jamis.rb'
There’s a couple of ‘nice’ features here. First of all, if you auto_require
something, it will use the module name to construct the full require
call. So “Jamis::Buck.auto_require 'my_module'
” will result in a “require 'jamis/buck/my_module'
” call.
Second, if you “auto_require 'buck'
” and there is both a buck
directory and a buck.rb
file the require
will be treated differently. The resulting require
call guarantees your particular buck.rb
file is loaded even if there is another buck.rb
file in the $LOAD_PATH
that has already had “require 'jamis/buck'
” called against it.
You may be wondering why I didn’t just add the auto_require method to the Module
class. I could have, but it makes me feel dirty.
The funny thing is, in order for this to be useful to me, I’ll either have to ship and… um… require
a file that contains Dependencies::AutoRequire
with each of my plugins – or I’ll have to convince the rails core team that Dependencies::AutoRequire
would be beneficial to more than just me. Well, I think it’s funny…