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 ! |