Archive for May, 2007

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.

Note: There is a more elegant solution to the problem of mixing module/class methods into modules and classes in the update at the end of this post. However, if you are interested in how singleton classes work, the next paragraphs may also be worth reading.

Fortunately, we can work around this. Ruby has a feature called singleton classes (also known as metaclasses). Singleton classes are used to add methods to objects. Normally, the methods provided by an object are determined by it’s class. You can’t store methods directly in an object. But as we know, Ruby allows us to do this. You can define a method that belongs only to a specific object.

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 singleton class, not in the module itself.

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:

Comments (6)