Ancestors in Ruby
This was the tip of the week in the July 29, 2021 Ruby Weekly Newsletter.
Ruby’s object model is fascinating! In the next few tips following this one, we’ll look at different ways of using code from a module
in a class: include
, extend
and prepend
.
In order to set ourselves up to understand the nuances behind each approach, we need to first understand Ruby’s concept of ancestors, and how they are used. We’ll use a small empty Example
class to start:
class Example
end
Now let’s call #to_s
on an instance of a Example
:
Example.new.to_s
=> "#<Example:0x00007fcc5997ec30>"
How did this happen? We never defined Example#to_s
. Ancestors are an ordered array of classes or modules that are included or prepended in a given module or class. Let’s break that down. In this case, we can see Example#ancestors
:
Example.ancestors
=> [Example, Object, Kernel, BasicObject]
When Ruby is looking for a method defined on an object, it will traverse these ancestors in order looking for the method. So in our example, Ruby will not see #to_s
defined on Example
, and will traverse to look for it defined on Object
. It is defined on Object
, so Ruby uses that!
If we, however, do define it within Example
:
class Example
def to_s
"We created this!"
end
end
Example.new.to_s
=> "We created this!"
We can confirm it will use our definition of #to_s
within Example
before defering to looking for #to_s
later in the ancestors chain. Check out next week’s tip to see how ancestors are relevant to include
!