From time to time, I blog about the different things I learn in Ruby on Rails. Recently, I have been engrossed in the metaprogramming aspect of rails. There are some excellent tutorials on the web, but the hierarchy of how methods are called when classes or objects are extended is not discussed thoroughly anywhere. I ran many experiments and found this for myself, so hope that other people will be able to learn from this as well.
Extend vs. Include
“extending” a class with a module adds methods from the module into the class as class methods.
“including” a module into a class adds methods from the module into the class as instance methods.
There is a lot of discussion around this concept, and this is simple to demonstrate. So, if you ‘extend’ a class (say Foo) with a module with method (say bar), then you can call Foo.bar. However, if you on include the module into the class, then you can call foo.bar, where foo is an instance of the class Foo. This is pretty simple to understand.
However, what happens if you already have a method called bar in the original definition of the class. In that case, if you ‘include’ the module into the class, and then call foo.bar(), which method gets called? the bar method in the class definition, or the one defined in the module?
According to the ruby method name resolution algorithm, Ruby searches through the following steps for a name resolution:
- As the first step, Ruby checks the eigenclass of o for singleton method named m.
- If the method named m is not found in the eigenclass, Ruby search the class of o for an instance method name m.
- If the method m is not found in the class, Ruby searches the instance methods of any modules included by the class of o. If there are modules included, they searched in the reverse of the order which they are included.
- If no instance method m is found in the class of o or in its modules, then the search moves up the inheritance hierarchy of the super class. Step 2 and 3 are repeated for each class in the inheritance hierarchy until each ancestor class and its included modules have been searched.
- If no method named m is found after completing the search, then a method named method_missing is invoked instead. In order to find an appropriate definition of this method, the name resolution algorithm starts over at step 1. The Kernel module provides a default implementation of method_missing, so this second pass of name resolution is guaranteed to succeed.
According to this, the method bar defined in the class will win over the method included in the class. Note that if you extend the class with the module, and there is a method defined in the class as ‘self.bar’ will prevail over the one in the module.
This is pretty easy to understand. However, this gets confusing when the instance of the class itself is extended with a module.
module Mod def hello "Hello from Mod.\n" end end class Klass def hello "Hello from Klass.\n" end end k = Klass.new k.hello #=> "Hello from Klass.\n" k.extend(Mod) #=> #<klass :0x401b3bc8> k.hello #=> "Hello from Mod.\n" </klass>
As you can see from the above example, if the object itself is extended with the module, then the method from the module wins over the one defined in the class.
Confusing, isn’t it?