Forked from @damien-roche's gist. Here's my addendum.
Method lookup is a simple affair in most languages without multiple inheritance. You start from the receiver and move up the ancestors chain until you locate the method. Because Ruby allows you to mix in modules and extend singleton classes at runtime, this is an entirely different affair.
I will not build contrived code to exemplify the more complicated aspects of Ruby method lookup, as this will only serve to confuse the matter. If you are having trouble following method lookup in your own programs, it is not because Ruby has strange rules (it does), it is because your code is too tangled.
When you pass a message to an object, here is how Ruby finds what method to call:
Simply put, a singleton method is a method that is defined on an instance as oppose to a class where the method would be available on all instances.
This takes precedence even over methods defined on the class.
Here is an example to demonstrate:
class MyCar
def method; 'defined on car'; end
end
object = MyCar.new
def object.method
'defined on singleton'
end
object.method # => 'defined on singleton'
If we can't find the method on the singleton, we'll take a look in any modules that extend the singleton.
You can extend singletons like so:
module MyModule
def method; 'defined on MyModule'; end
end
object = MyCar.new
object.extend(MyModule)
object.method # => 'defined on MyModule'
If you extend with multiple modules, later modules take precedence:
module MyModule2
def method; 'defined on MyModule2'; end
end
object.extend(MyModule)
object.extend(MyModule2)
object.method # => 'defined on MyModule2'
Considering you rarely extend singletons, this is usually where you would start in practice:
class MyCar
def method; 'defined on MyCar'; end
end
MyCar.new.method # => 'defined on MyCar'
So, the method doesn't exist within the class, but we mixed in a bunch of methods from a few modules:
module A
def method; 'defined on A'; end
end
module B
def method; 'defined on B'; end
end
class MyCar
include A
include B
end
Similar to when we extend a singleton, the later modules take precedence:
MyCar.new.method # => 'defined on B'
Ruby 2.0 introduced a prepend
method to accompany include
. Methods defined in a prepended module take precedence over methods defined on the class.
module A
def method; 'defined on A'; end
end
class MyCar
prepend A
def method; 'defined on MyCar'; end
end
# with include A
MyCar.new.method # => 'defined on MyCar'
# with prepend A
MyCar.new.method # => 'defined on A'
Ok, so we can't find the method on the singleton, we can't find it within the class definition, and we can't find it within any mixed in modules. What now? Well, we move up the ancestors chain and start from (3). Why not (1) you might ask? That's because the first two lookups involve singletons, whereas your ancestors are just modules/classes, not instances. This means we start with methods on the superclass, then lookup modules mixed into that class, and on we go to the next superclass.
We've reached the end of the ancestors chain and still can't find our method. What now? Ruby will now return back to (1) and run method_missing
. To give an example:
class BasicObject
def method; 'defined on Object'; end
end
class MyCar
def method_missing(name, *args, &block)
"called #{name}"
end
end
MyCar.ancestors # => [MyCar, Object, Kernel, BasicObject]
MyCar.new.method # => 'defined on Object'
The super
method looks up the ancestors chain for the method name where super
is called. It accepts optional arguments which are then passed onto that method. This will follow the same lookup path as outlined above from when the super
method was called.
class Vehicle
def method; 'defined on Vehicle'; end
end
class MyCar < Vehicle
def method
"defined on MyCar\n"
super
end
end
MyCar.new.method
# => "defined on MyCar"
# => "defined on Vehicle"
There is a rule that is used to think about the above behaviour called "one step to the right, and up", which can be described as: go right into the receiver's class (singleton), then up the ancestors chain. Here is a simple visualisation to further clarify:
Object
^
|
Parent class A
^
|
Module included by Parent Class B
^
|
Parent class B
^
|
object's class
^
|
obj -> obj's singelton_class
Here are a few methods to help you inspect the above in your own applications:
Array.ancestors
# => [Array, Enumerable, Object, Kernel, BasicObject]
# note: below returns Object because Enumerable is mixed in module (not a superclass)
Array.superclass
# => Object
# This will return all instance methods from the class, modules, superclasses
# (i.e. anything available to the class)
Array.instance_methods
# => [:huge, :list, :of, :methods]
# `false` argument below will only return methods defined within the Array class
Array.instance_methods(false)
# The following shows where the instance method is defined (Enumerable)
Array.instance_method(:reduce)
# => #<UnboundMethod: Array(Enumerable)#reduce>
# `false` argument below returns methods defined only the singleton
arr = Array.new
def arr.new_method; end
arr.methods(false)
# => [:new_method]
- https://practicingruby.com/articles/method-lookup-1
- https://practicingruby.com/articles/method-lookup-2
- http://tech.pro/tutorial/1149/understanding-method-lookup-in-ruby-20
- http://stackoverflow.com/questions/23848667/ruby-method-lookup-path-for-an-object
- http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html
I'm not sure how accurate this is in terms of terminology, but I think we could generalize these rules to a single one:
Walk up the ancestor chain of the object's singleton class and look for methods defined on them. If all else fails, start again with
method_missing
.
(ignoring modules for a moment)
Essentially, Ruby will search object.singleton_class.ancestors
for the first place where the method is defined, applying the module rules at each stage.
I came across this while trying to apply these principles to class methods (SomeClass.some_method
), since classes are also objects in Ruby.
class Class
def hi = "Class hi"
end
class Parent
def self.hi = "Parent (singleton) hi"
end
class Child < Parent
end
p Child.hi # => "Parent (singleton) hi"
If we remove the Parent.hi
method, Class#hi
is found. This makes sense (Child
inherits from Parent, and is an instance of Class
), but not exactly according to your rules; according to your rules, after Child
's singleton class and modules were checked, Class
should have been checked (rule 3) before the ancestor Parent
's singleton class (rule 5). So I tried to dig some more:
p Child.singleton_class
# => #<Class:Child>
p Child.singleton_class.ancestors
# => [#<Class:Child>, #<Class:Parent>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
object = Child.new
p object.singleton_class
# => #<Class:#<Child:0x00000181853d05b0>>
p object.singleton_class.ancestors
# => [#<Class:#<Child:0x00000181854095e0>>, Child, Parent, Object, Kernel, BasicObject]
You can see that for a regular object, the relevant chain (singleton_class.ancestors
) is what you describe (singleton, then ancestors). But for a class, it's a chain of singleton classes for each ancestor, which then leads back into Class
and its ancestors.
So, in summary, rle 3 is actually an instance of rule 5, not a separate thing. Ruby moves up the singleton class ancestor chain. For regular objects, it happens to only contain the singleton class and then regular ancestors, but for classes, it will have a mini-chain of multiple singleton classes first.