Credit: Phil Tomson
Problem
You've written a module that gets mixed into classes. Your module has some initialization code that needs to run whenever the mixed-into class is initialized. You do not want users of your module to have to call super in their initialize methods.
Solution
First, we need a way for classes to keep track of which modules they've included. We also need to redefine Class#new to call a module-level initialize method for each included module. Fortunately, Ruby's flexibility lets us makes changes to the built-in Class class (though this should never be done lightly):
class Class def included_modules @included_modules ||= [] end alias_method :old_new, :new def new(*args, &block) obj = old_new(*args, &block) self.included_modules.each do |mod| mod.initialize if mod.respond_to?(:initialize) end obj end end
Now every class has a list of included modules, accessable from the included_modules class method. We've also redefined the Class#new method so that it iterates through all the modules in included_modules, and calls the module-level initialize method of each.
All that's missing is a way to add included modules to included_modules. We'll put this code into an Initializable module. A module that wants to be initializable can mix this module into itself and define an initialize method:
module Initializable def self.included(mod) mod.extend ClassMethods end module ClassMethods def included(mod) if mod.class != Module #in case Initializeable is mixed-into a class puts "Adding #{self} to #{mod}'s included_modules" if $DEBUG mod.included_modules << self end end end end
The included callback method is called whenever this module is included in another module. We're using the pattern shown in Recipe 9.3 to add an included callback method into the receiving module. If we didn't do this, you'd have to use that pattern yourself for every module you wanted to be Initializable.
Discussion
That's a lot of code, but here's the payoff. Let's define a couple of modules which include Initializeable and define initialize module methods:
module A include Initializable def self.initialize puts "A's initialized." end end module B include Initializable def self.initialize puts "B's initialized." end end
We can now define a class that mixes in both modules. Instantiating the class instantiates the modules, with not a single super call in sight!
class BothAAndB include A include B end both = BothAAndB.new # A's initialized. # B's initialized.
The goal of this recipe is very similar to Recipe 9.8. In that recipe, you call super in a class's initialize method to call a mixed-in module's initialize method. That recipe is a lot simpler than this one and doesn't require any changes to built-in classes, so it's often preferable to this one.
Consider a case like the BothAAndB class above. Using the techniques from Recipe 9.8, you'd need to make sure that both A and B had calls to super in their initialize methods, so that each module would get initialized. This solution moves all of that work into the Initializable module and the built-in Class class. The other drawback of the previous technique is that the user of your module needs to know to call super somewhere in their initialize method. Here, everything happens automatically.
This technique is not without its pitfalls. Anytime you redefine critical built-in methods like Class#new, you need to be careful: someone else may have already redefined it elsewhere in your program. Also, you won't be able to define your own included method callback in a module which includes Initializeable: doing so will override the callback defined by Initializable itself.
See Also
Strings
Numbers
Date and Time
Arrays
Hashes
Files and Directories
Code Blocks and Iteration
Objects and Classes8
Modules and Namespaces
Reflection and Metaprogramming
XML and HTML
Graphics and Other File Formats
Databases and Persistence
Internet Services
Web Development Ruby on Rails
Web Services and Distributed Programming
Testing, Debugging, Optimizing, and Documenting
Packaging and Distributing Software
Automating Tasks with Rake
Multitasking and Multithreading
User Interface
Extending Ruby with Other Languages
System Administration