Ruby's method lookup algorithm is actually really simple:
- retrieve the
class pointer of the receiver
- if the method is there, invoke it
- otherwise retrieve the
superclass pointer, and repeat
That's it.
If the algorithm comes to a point where there is no more superclass, but it still hasn't found the method yet, it will restart the whole process again, with method_missing as the message and the name of the original message prepended to the arguments. But that's it. That's the whole algorithm. It is very small and very simple, and it has to be very small and very simple because method lookup is the single most often executed operation in an object-oriented language.
Note: I am completely ignoring Module#prepend / Module#prepend_features since I just don't know enough about how it works. I only know what it does, and that's good enough for me.
Also note: I am ignoring performance optimizations such as caching the result of a method lookup in something like a Polymorphic Inline Cache.
Okay, but here's the trick: where exactly do those class and superclass pointers point to? Well, they do not point to what the Object#class and Class#superclass methods return. So, let's step back a little.
Every object has a class pointer that points to the class of the object. And every class has a superclass pointer that points to its superclass.
Let's start a running example:
class Foo; end
Now, we have class Foo, and its superclass pointer points to Object.
foo = Foo.new
And our object foo's class pointer points to Foo.
def foo.bar; end
Now things start to get interesting. We have created a singleton method. Well, actually, there is no such thing as a singleton method, it's really just a normal method in the singleton class. So, how does this work? Well, now the class pointer points to foo's singleton class and foo's singleton class's superclass pointer points to Foo! In other words, the singleton class was inserted in between foo and its "real" class Foo.
However, when we ask foo about its class, it still responds Foo:
foo.class #=> Foo
The Object#class method knows about singleton classes, and simply skips over them, following the superclass pointer until it finds a "normal" class, and returns that.
Next complication:
module Bar; end
class Foo
include Bar
end
What happens here? Ruby creates a new class (let's call it Barʹ), called an include class. This class's method table pointer, class variable table pointer, and constant table pointer point to Bar's method table, class variable table, and constant table. Then, Ruby makes Barʹ's superclass pointer point to Foo's current superclass, and then makes Foo's superclass pointer point to Barʹ. In other words, including a module creates a new class that gets inserted as the superclass of the class the module is included into.
There's a slight complication here: you can also include modules into modules. How does that work? Well, Ruby simply keeps track of the modules that were included into a module. And then, when the module is included into a class, it will recursively repeat the steps above for every included module.
And that's all you need to know about the Ruby method lookup:
- find the class
- follow the superclass
- singleton classes insert above objects
- include classes insert above classes
Now let's look at some of your questions:
When calling methods explicitly on a class, there are lots of illustrations on the order in which the classes, and modules included by them are searched (and thus exactly what super calls in each case). But when not explicitly calling a method, e.g. a plain func args rather than self.func args what is the search order?
The same. self is the implicit receiver, if you don't specify a receiver, the receiver is self. And parentheses are optional. In other words:
func args
is exactly the same as
self.func(args)
Why does in my example below, the member method calling func find the member method before the global, but func2 finds the global without method_missing being called?
There is no such thing as a "global method" in Ruby. There is also no such thing as a "member method". Every method is an instance method. Period. There are no global, static, class, singleton, member methods, procedures, functions, or subroutines.
A method defined at the top-level becomes a private instance method of class Object. Test inherits from Object. Run the steps I outlined above, and you will find exactly what is going on:
- Retrieve
x's class pointer: Test
- Does
Test have a method called func: Yes, so invoke it.
Now again:
- Retrieve
x's class pointer: Test
- Does
Test have a method called func2: No!
- Retrieve
Test's superclass pointer: Object
- Does
Object have a method called func2: Yes, so invoke it.
And when the global is instead an module/class/type, why is the member with the same name not found at all?
Again, there is no global here, there are no members here. This also doesn't have anything to do with modules or classes. And Ruby doesn't have (static) types.
Math
is a reference to a constant. If you want to call a method with the same name, you have to ensure that Ruby can tell that it's a method. There are two things that only methods can have: a receiver and arguments. So, you can either add a receiver:
self.Math
or arguments:
Math()
and now Ruby knows that you mean the method Math and not the constant Math.
The same applies to local variables, by the way. And to setters. If you want to call a setter instead of assigning a local variable, you need to say
self.func = 'setter method'