The object model and method lookup
ancestors
ancestors is the method lookup order, printed as a list. The first entry is the class itself, then the modules it includes, then its superclass and so on:
ruby -e 'p String.ancestors.first(5)'
[String, Comparable, Object, Kernel, BasicObject]
A call on a string is looked up in String, then Comparable, then Object. That is why < works on a string: it comes from Comparable, which uses the <=> that String defines.
prepend
include puts a module after the class; prepend puts it before, so the module can wrap the class's own method and call super into it:
ruby -e 'module M; def hi = "M"; end; class A; def hi = "A"; prepend M; end; p A.ancestors.first(3); p A.new.hi'
[M, A, Object] "M"
The module wins because it is first in the list. A prepended module that calls super gets the class's version, which is the mechanism behind a decorator that does not replace the method.
Singleton classes
A method defined on one object lives in that object's singleton class, which sits in front of its real class for that one object only:
ruby -e 'class A; def hi = "A"; end; o = A.new; def o.hi = "single"; p o.hi; p A.new.hi'
"single" "A"
o.singleton_class shows the class, and o.singleton_class.ancestors puts it before A. A class method is the same idea one level up: A is an object, and def self.x defines a method on its singleton class.
The lookup path
A call on an object goes through these steps, and knowing the order answers most "why did this method run?" questions:
- The object's singleton class, if it has methods.
- The class itself.
- Each prepended module, in reverse order of prepending.
- Each included module, in reverse order of inclusion.
- The superclass chain, each with its own prepends and includes.
- method_missing, if nothing matched.
obj.method(:hi).owner answers which class or module supplied the method, which is faster than reading the list when a framework has added three layers.
What to watch for
Adding a module with include twice is harmless;
prepending twice puts the module in the chain twice, and
super calls it twice. Guard a prepend with a
check or make the module idempotent.
super without arguments forwards the same
arguments; super() passes none. The difference is
the first thing to check when a wrapper changes a method's behaviour.
Next: method_missing done right
Article author: Arthur Isaev