Mixin Module Methods
In Ruby, a module can be included in other modules or classes which adds the features of the included module to the including module or class. This works fine if I want to mixin instance methods into a class.
module Hello
def say_hello
puts "Hello, World! Here is #{self.to_s}."
end
end
class Person
include Hello
def initialize(name)
@name = name
end
def to_s
"#{@name}, a Person."
end
end
Person.new("Stefan").say_hello
=> Hello, World! Here is Stefan, a Person.
The include Hello statement in the Person class causes the Ruby interpreter to add all instance methods that are defined in Hello to Person. This works well for instance methods but things get a bit more tricky if we want to mixin module or class methods. The following code looks straightforward, but unfortunately doesn’t work.
module Hello
def self.say_hello
puts "Hello, World! Here is #{self.to_s}."
end
end
module Stefan
include Hello
end
Stefan.say_hello
=> undefined method `say_hello' for Stefan::Module (NoMethodError)
Here we define a module method say_hello in the module Hello and then include Hello in Stefan, hoping that say_hello will be mixed into Stefan as a module method. But Ruby reports a NoMethodError when we try to send the say_hello message to Stefan. Reading the documentation reveals the problem: include works only for instance methods (and constants and module variables). Hence our module method say_hello isn’t included. Bad Luck.
Fortunately, we can work around this. Ruby has a feature called
stefan = Object.new def stefan.say_hello puts "Hello, World! Here is Stefan." end stefan.say_hello => Hello, World! Here is Stefan.
In this example, we create a new object ’stefan’ and add a method say_hello to ’stefan’. Now how does this work if we can’t store methods in an object? The solution is that the method is not added to the object ’stefan’ but to it’s singleton class. This class does not affect an object’s inheritance chain, ’stefan’ is still an Object. A singleton class intercepts the messages send to an object before it goes up the inheritance chain. If the singleton class can respond to a message it will do so. If not, the message is passed up through the inheritance chain to see if another class can handle that message.
Back to our module mixin problem. In Ruby everything is an object. A module is really an object of class Module. And as every object, a module can also have a singleton class. If we define a module method, the method isn’t really added to the module object but to it’s singleton class. The same happens for class methods, by the way. In fact, we could write a module method as follows.
module Stefan
class << self
def say_hello
puts "Hello, World! Here is Stefan."
end
end
end
Stefan.say_hello
=> Hello, World! Here is Stefan.
The class << self syntax opens a singleton class, in this case for the enclosing module Stefan. In the singleton class, we define the method say_hello. The same happens behind the scenes if you write def self.say_hello. If we now send the message ’say_hello’ to Stefan, first the module’s singleton class will get the opportunity to respond to the message. Since our singleton class has a method say_hello, it will handle the message by calling this method. If the singleton class doesn’t know how to respond to a message, Stefan’s class (Module) will get the chance to handle it. Be aware that Stefan is a module, and a module is an object of class Module!
With this in mind, we now have a solution for mixing module methods into a module. We first have to define a module that provides the methods to be included. We than include this module into another module’s
module Hello
def say_hello
puts "Hello, World! Here is #{self.to_s}."
end
end
module Stefan
class << self; include Hello; end
end
Stefan.say_hello
=> Hello, World! Here is Stefan.
Et voila, say_hello is now a module method of Stefan. Note that say_hello is defined in Hello as an instance method, not as a module method, and how Hello is included in Stefan. This technique works for mixing class methods into classes as well.
Update (May 19, 2007):
Gregory Brown commented about a much cleaner solution: simply use object.extend which adds all instance methods from the modules given as parameter to object. Since modules and classes are objects, we can send them the extend message.
module Hello
def say_hello
puts "Hello, World! Here is #{self.to_s}."
end
end
module Stefan
extend Hello
end
Stefan.say_hello
=> Hello, World! Here is Stefan.
Works like the same but looks much cleaner than my solution. As Gregory pointed out, we could also use a combination of extend and include to mixin instance and module methods at the same time.
module Hello
module ClassMethods
def say_hello
puts "Hello, World! Here is #{self.to_s}."
end
end
def included(base)
base.extend(ClassMethods)
end
end
module Stefan
include Hello
end
Stefan.say_hello
=> Hello, World! Here is Stefan!
In this example, the ‘included’ callback is used. This callback is invoked on a module whenever it is included in another module or class. The parameter ‘base’ holds the including module or class. Note that the say_hello method is placed in a separate module ClassMethods. The example uses the included callback to extend base with the methods from this module when base includes Hello. This way, the methods defined in ClassMethods become module methods of Stefan when it includes Hello.
Gregories solution is obviously much more elegant than my approach since you don’t need to cope with singleton classes. To my excuse, I wasn’t aware of the extend methods and it’s function. However, at least I learned a lot about singleton classes when writing this post :-).
Links:
- Seeing Metaclasses Clearly for a great and fun to read introduction to metaclasses (just another name for singleton classes).
- The Ruby singleton class for another good introduction to singleton classes.
- Mixing in Class Methods for another blog post about mixing in class methods.
- Why the lack of mixing-in support for Class methods? A discussion in the ruby forum about why class methods are not included.