Ruby's object model

I joined Shopify a couple of months ago, where I work on the world’s largest Rails app.

I was super new to Ruby and the Rails magic scared the shit out of me. Understanding how objects work in Ruby was crucial to writing good software.

Weird thing about ruby is that the object can adopt behaviors that are not defined by their classes, which is the central defining principal in Ruby programming language. It’ll make a lot of sense once we understand how objects work here.

Let’s get started.

Every object in ruby needs to know how to respond to the message sent to it.

class Person
  def initialize(name)
    @name = name
  end

  def speak
    p "#{@name} says hello"
  end
end

The instructions on how to respond to the message is defined in the object’s class.

For example, when we run

yask = Person.new("Yask")
yask.speak

>>  "Yask says hello"

How method look up works ?

  1. Set self to the receiver.
  2. Find out the self’s class.
  3. Search for the method defination in that class.
    • If it’s not found there, move to the parent of this class. Keep repeating this step untill the method is found.
    • Execute the method once found.
  4. If the method was not found, start looking for missing_method.

Beautiful thing about this 4 step process is that this method is universal for any object.

Since class is an Object in Ruby too, it follows the same process.

How class method works ?

They work exactly the same way.

Eg:

Person.object_id

The method object_id is looked up in the following classes :

self = Person

Person.class.ancestors

#> [Class, Module, Object, Kernel, BasicObject]

Don’t believe me ?

class Module
  def object_id
    p 'how about now ?'
  end
end

Person.object_id

#> 'how about now ?'

Singleton / Eigen / Metaclasses

This is a nameless class which we can attach in between any object’s class chain.

Eg: If the object chain looks like this:

String.class.ancestors

#> [Class, Module, Object, Kernel, BasicObject]

After adding the Eigen class it would be :

#> [NamelessEigenClass, Class, Module, Object, Kernel, BasicObject]

So if we wanted to add a function to the class String, we can add it to the EigenClass.

To do that:

class String
  class << self
    def my_custom_method
      #do_something
    end
  end
end

#Now we can execute:

String.my_custom_method

If we follow the steps for how method look up works, it makes sense.

self = String

self.class = Class

After we created the Eigen class the object chain looked like : [NamelessEigenClass, Class, Module, Object, Kernel, BasicObject]

And it found the my_custom_method at NamelessEigenClass.

Inheritence

What happens when we inherit from a parent class which opens up an Eigen class and defines some methods there ?

Like, inheriting class methods from the parent class. How does that work ?

class Parent
  class << self
    def speak
      p 'hello world'
    end
  end
end

class Child < Parent
end

As you can guess Child.speak works, but how ?

We never defined the eigen class in Child, and in the process of method look ups ruby would look at Child.class which is Class (and it’s parents).

Since we never defined singleton class for it, it shouldn’t be able to search for it.

But intrestingly, when we inherit from a parent class, the child class’s Eigen class also inherits from it’s Parent’s eigen class.

We can prove this:

Child.singleton_class.superclass
#> #<Class:Parent>

Child.singleton_class.instance_methods.detect &:hello.method(:==)
#> :speak

Isn’t that cool ?

Method looks ups are super simple and there is one and only one rule of them. No exceptions.