Automatically Initializing Mixed-In Modules

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

  • Recipe 9.3, "Mixing in Class Methods"
  • Recipe 9.8, "Initializing Instance Variables Defined by a Module"


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



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net