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!