What is the method lookup path in Ruby?14 Mar 2016
A simple question but hard to answer. Why is that hard? Because Ruby has various ways of defining a method and add it to a class:
- Adding it to the singleton class
- Adding it to the class
- Include a module
- Prepend a module
- Extend a module
- Inherit from superclass
If this sounds complicated to you then that’s because it is.
So first rule: try to avoid such situations where you have a multitude of classes and modules defining the same method.
If you have more than two definitions of a method then you most likely have bigger problems than knowing about the lookup path. Also I haven’t seen many good uses of adding a method to the singleton class so far.
So how do we go about finding the lookup path? How about a small piece of code that answers this question?
What does this code do? It defines a method
call for the six possibilities described above. They all print out some debugging info and then forward the call to
super. Since at the end of the hierarchy the
call method is not implemented I added
rescue nil. Of course this would only be required for the last element in the hierarchy. But we don’t know which one this is, yet. Lets run the code and see the output:
What if you extend or include or prepend multiple times? The last definition comes first. That is if you have:
then the definitions from
Baz will take precedence.
And of course if you do not call
super then none of the other implementations will be called.
So, now that this is solved…let’s look at another way this can be determined:
ancestors. The documentation says that this “Returns a list of modules included in mod (including mod itself).”.
If we extend above code to print the list of ancestors:
Then we can see following:
This is the order that we determined before but not complete. We are missing the methods that have been added to the singleton class. Those can be seen if we check the
instead (note that this will create the singleton class if it does not yet exist):
This will print the full list of ancestors:
The #<Class:#<Klass:0x007fe34b225480>> is the singleton class. It exists solely for this object:
This ancestry also shows how Ruby looks up methods. It does not make complicate decisions of where to look first. It just walks up the hierarchy and calls the first matching method it can find. So if the singleton class does not respond to the method, then the prepended modules will be checked until the root is reached.