ruby_cool_kid.rb — Meta Programming series: Like Object, Like Class. Ruby Ancestors II

Pablo Adell
8 min readOct 3, 2021

--

Photo by Joshua Fuller on Unsplash

In the past story we talked about how everything in Ruby is a constant and Classes are nothing more than objects. Meaning that we could store a Class inside a constant, following its behaviour as an object.

We also said that everything in Ruby is a constant, but we can have classes inside a Module and classes within classes.

But, how does ruby arrange it so they don’t collide and our program says: “Hasta la vista”?

Let’s start first with the concept of a constant.

A constant can be told apart by the uppercase letter at the beginning of its name and is suppose to have an inmutable value. It is quite similar to a variable, to the point that you can change its value (even though you will get a warning from the interpreter).

But what is it that differentiates constants from variables? Scopes

In Ruby, constants are arranged as if they formed a filesystem. Each constant would be defined inside a module or a class and have its own path. This behaviour is quite similar to the behaviour of files and directories. You could have the same name for different constants, as long as they are on different modules/classes.

Let’s pop-out our whiteboard and see in with an example, shall we?

Imagine we are testing this piece of code, and we are asked to draw a figure to show how the constants would be arranged. The result would be as follows:

Tree of the module and its constants

In the tree above, we can see that there are two constants named Wheels, even though they are not the same. If we thought of it as files, there would be one file named Wheels at the route ./Vehicles/Wheels and the other Wheels file would be at ./Vehicles/Motorbike/Wheels.

This metaphor is really nice, but, how does it work on a Ruby program? Easy, instead of using /, we use a double colon :: to separate constant’s path. Translating this into our code, the result would be:

Ruby also allows referencing a root-level constant, if we are too deep in the tree. To do so, we just put :: before the name of the constant and the Ruby interpreter would look for it at the root level.

Moving on to practical use of these paths, if we wanted to know all the constants defined at the current scope of a module, we could call constants on the module, which will return all constants, including class names.

Now that we know it all about Ruby classes, modules, objects, and constants, it is time to level up again and put it all together.

What happens when you call a method in Ruby?

This question may sound overwhelming at first, so, let’s divide and conquer it. Obviously, when a method is called two things are happening:

  • Ruby tries to find the method → This is called method lookup
  • If found, execute the method → On a thing called self

This process is not Ruby exclusive, as it happens for all OOP. However, understanding how it is done in Ruby opens the door to a new level of skills. Follow me to find the key to that door 😉

Method lookup

We already know from our past story that methods live inside classes and objects store a reference to its class.

In Ruby, when a method is called on an object, Ruby first looks inside the object’s class. If the method is not found there, it will go up in the ancestor’s chain and try again.

Wait, wait, wait. Ancestor what? 😵

Ancestors chain. This concept might sound new for some readers, so before going any further on method lookup, let’s introduce two key concepts, receiver and ancestor's chain.

The receiver is nothing more than the object you call the method. The one that receives the method.

In this example, lucky_number is the receiver of the :to_s and :is_a?methods.

However, if we look at the instance_methods of it (being the instance methods of Integer as it is the class of the object lucky_number), we will see that is_a? is not among them:

Where does it call is_a? then? It looks for it on the ancestors_chain

Ancestors chain

Understanding the concept of ancestor’s chain is as easy as understanding the concept of classes and superclasses.

Think of a class in Ruby, then move into its superclass, then into the superclass’ superclass, and keep going up until you reach BasicObject, the root of the hierarchy in Ruby.

This path you just traversed is the ancestor’s chain. The path also includes modules, not only classes.

Having this new knowledge, we can understand how is_a? is called and where. Let’s draw the ancestors chain for lucky_number

Ancestors chain of lucky_number

Of course, do not mistake this diagram for a class diagram, all elements in the diagram are objects. Some objects happen to be classes and be linked through the :superclass method. If we were to show Integer class ancestors the output would be something like:

Ancestors chain of Integer

What are Comparable and Kernel doing there? Those are not classes right. True, modules are also in the ancestor’s chain, and so, are evaluated in the method_lookup, let’s talk about them.

Modules and method lookup

The ancestor's chain , as we said before, goes from class to superclass, but it can also include modules.

In Ruby, we have two different ways of including a module into our chain, either by calling include or prepend. What is the difference between these two?

  • include adds the module in the ancestor's chain right after or above (depending on how you imagine the chain), the class where it was included
  • prepend adds the module in the ancestor’s chain right before or below, the including class.

Let’s picture it:

The ancestor's chainwould look like this:

Ancestors chain for FloorOffice

Knowing this, we can now think of scenarios like including a method at theKernel level. If we add a method to Kernel , it will be available for all objects as it is always on its ancestor's chain.

What is the self in Ruby?

At this point we know how Ruby looks for methods, now we need to know how are they executed.

What is happening behind under the hood when a method that is not defined in the current class is called?

FavouriteTrilogy class
DisplayableTrilogy class

I am a big fan of history books, and one of my favourite writers is Santiago Posteguillo, who wrote a trilogy on the fearless general Publio Cornelio Scipio, Africanus.

With this little program above, I just wanted to store the trilogy and author in an object, printing author and trilogy by calling a method.

In this case, print_author_and_books a method in DisplayableTrilogy , will do it for me.

However, we are calling a method in a superclass with instance variables on another object of another class. So, how does Ruby know which object to call print_author_and_books on? Let’s make a quick drawing to illustrate what is happening:

Before going any deeper, let’s bring into action a concept we talked about on Metaprogramming Series: Dynamic Dispatching, sending. Whenever we are “calling” a method, what is really happening is that we are sending the name of the method to the object and waiting for a response. The object that receives the message with the method name is called the receiver. This concept, while not being complex, is crucial to understand how Ruby manages method calls.

On the same page as the receiver, it is key to introduce the reader to the concept of self. Every line of Ruby code is executed within an object, the current object. Ruby uses the keyword self to refer to it.

Self can only be one object at a time and that object does not usually hold on to it for a long time.

Now, hold tight to your seats. When you call a method, the receiver becomes self. Instance variables as well as methods (without explicit receiver) are now instance variables and methods of self. When a method is explicitly called in another object, that object becomes self.

With this knowledge, we can follow the flow that ou program followed. At first, the object that is stored in self is nil and then it is replaced by FavouriteTrilogy , right?

If you said yes, let’s say that you’d lost a million dollars if it was the final question on Who wants to be millionaire?.

The Top Level

True, it is not fair, I have not yet gotten into a Ruby classic, the Top Level. If you were to ask Ruby on a terminal what is self before even calling any method, the answer would be:

Calling self on a fresh IRB session

When we start a Ruby program, there is a basic object named main (sometimes also called top-level context) that the Ruby interpreter created for us. This is the object available when we are at the top level of the stack, meaning we have not called any method yet or all the methods have returned.

FYI: the role of self is taken by the class or module itself whenever we are in their definitions.

Summing up, when we call a method in Ruby, it first looks for the method by using method_lookup, then sends the method name to the receiver being self.

Then, going back to our drawing of FavouriteTrilogy :

At first, self would be main but will be swapped by Class when FavouriteTrology.new(..) is called.

Then, self would reference FavouriteTrilogy when favourite_trilogy.display_info is called, to finally be DisplayableTrilogy when print_author_and_books is called.

In the drawing above, you can see the different instance variables and methods inside each class and the transition of self between them.

And that’s all !! If you have read until the end of this story, well done !! I owe you a coffee to wake you up 😉

Stay tuned for the next story on ruby_cool_kid.rb: Metaprogramming Series we may be talking about supernatural things, like 👻 and lost methods.

Thanks a lot for reading my stories!! I would love to hear any feedback you can give me to improve the quality of them 💃. See you around!!

Pablo.

--

--

Pablo Adell
Pablo Adell

Written by Pablo Adell

Hello there!! My name is Pablo, I am a Full-Stack Software Engineer at Affirm, focused mainly in Ruby on Rails and React. I hope you enjoy my content!

No responses yet