The ancestors' chain in Ruby is an essential bit of knowledge for the ones who aim to achieve a good understanding of the language. Let's dive into details.
Consider the following example:
class Hotel end
We just created the empty
Hotel class. No methods are defined yet. See what happens when we check what methods are available to invoke:
Hotel.instance_methods # => [:remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, ...] Hotel.instance_methods.size # => 56
There are lots of methods that are predefined and are kept in classes and modules outside of our class
In fact, the
Hotel class has
Object as its superclass:
Hotel.superclass # => Object
The methods we just discovered belong to
When we invoke a method, Ruby will look for it in the current class/module. Otherwise, if the given method is missing from the current class/module, Ruby will look for the method in the next class/module. This forms the ancestors chain - the way the interpreter copes with method invocations within the class hierarchy.
To view the ancestors chain of a given class we make a call like this one:
Hotel.ancestors # => [Hotel, Object, Kernel, BasicObject]
If for some reason we include a module in our class, let’s say:
module Housekeeping end class Hotel include Housekeeping end
Then the ancestors' chain would change as follows:
Hotel.ancestors # => [Hotel, Housekeeping, Object, Kernel, BasicObject]
Any method in
Hotel whose name collides with a method name from the
Housekeeping module will be invoked over the one in
Housekeeping or elsewhere in the ancestors chain.
We can complicate the example:
module TaskHelper end module Housekeeping include TaskHelper end class Hotel include Housekeeping end
Hotel.ancestors # => [Hotel, Housekeeping, TaskHelper, Object, Kernel, BasicObject]
On the other side,
prepend will make the ancestors chain look for the method first in the module and then in the current class/module:
module Housekeeping end class Hotel prepend Housekeeping end
Hotel.ancestors # => [Housekeeping, Hotel, Object, Kernel, BasicObject]
Let’s take a look at the examples below:
class Building end class Hotel < Building end
Hotel.ancestors # => [Hotel, Building, Object, Kernel, BasicObject]
This one is self-explanatory, and now let’s see what happens when we mix modules and class inheritance:
module ManagementHelper end class Building include ManagementHelper end module Housekeeping end class Hotel < Building include Housekeeping end
Hotel.ancestors # => [Hotel, Housekeeping, Building, ManagementHelper, Object, Kernel, BasicObject]
Kernel mixed in and
BasicObject as its superclass.
In some bizarre cases, we need to avoid namespace pollution from the
This is done when we inherit from
BasicObject directly - the parent of all classes in Ruby.
Here is how the ancestors chain is different from usual having such parent class:
class Hotel < BasicObject end Hotel.ancestors # => [Hotel, BasicObject]
The usage of this technique exceeds the scope of this blog post.
The recursive rule by which the elements in the ancestors' chain list are ordered may be formulated like so:
prepend is excluded from the aforementioned description for simplicity but the only change it brings is that it forces Ruby to look first in the prepended module instead of the current class/module.
The last step when no suitable method was found is to invoke
BasicObject#method_missing. More about it here: method_missing explained