Ruby Object Model

17 May 2020 | categories: blog

prev: Shell Startup Files | next: Default Text Width

Ruby is pretty great, I think it’s a magical language especially when you wrap your head around the metaprogramming part. If you have used ruby before you will know that everything in ruby is an object, so you can call methods on everything, and yes that includes strings and integers.

"hi there".reverse     # => "ereht ih"
1.even?                # => false

What really helped me understand ruby and its idiosyncrasies was learning the Ruby Object Model, if you understand ruby’s object model and how method look up works it makes some other parts of the language much easier to understand. For example, ruby’s Eigenclass also known as Singleton class.

My goal here is to layout what ruby’s object model is and along the way hopefully help you to understand what more “advanced” syntax like this is doing:

class MyClass
  class << self
    def my_method
      "wtf?"
    end
  end
end

calling a method

Let’s start with something very basic, I’ll create a class and from that class instantiate an object.

class Person
  def speak
    "hola"
  end
end

skip = Person.new
# => #<Person:0x00005573832d0db0>
skip.speak
# => "hola"

I created a new Person and I made them speak, but how does ruby know where to find the method :speak. In fact, what happens if we call a method that we haven’t defined in our class?

skip.object_id
# => 46977305183960
skip.meep
# => NoMethodError: undefined method `meep' for #<Person:0x00005573832d0db0>

Interesting, so there appears to be more methods available than just the speak method defined in Person and ruby is smart enough to tell us meep doesn’t exist.

There are three scenarios here:

Let’s start with the first one

method in the object’s class

When we call speak on skip you can think of the message heading off to the right of the object to check the object’s class for the method.

Ruby checks in this class for a method called speak and finds it so ruby runs the code. Nice and easy so far. That diagram is too simple, let’s see if we can add some more to it.

method defined elsewhere

What happens when we call a method which isn’t defined in Person, i.e. :object_id?

If ruby goes right into the object’s class and cannot see the method it is after, then it will go up the chain of superclasses searching each as it goes. In other words it will check the parent of Person, and keep doing so until it finds the method it is after.

Ruby finds a method called :object_id in class Object so it runs that method.

Where did these classes come from? In ruby all classes inherit from Object, there might be some modules or classes before that but generally if you go down far enough you’ll hit Object and BasicObject.

calling a nonexistent method

This is all making sense so far, you call a method on an object and eventually it is found and code is ran. What happens then if we call a method that ruby cannot find, like skip.meep from earlier?

Ruby does its thing, right into the object’s class then up the chain of superclasses looking for #meep, it’s a pointless endeavour though because that method doesn’t exist in our chain.

When ruby reaches BasicObject and cannot find :meep it then starts over by calling :method_missing on the original receiver skip and the process starts over. Right into the class, up the chain until it reaches BasicObject where it finds :method_missing and the code is ran.

The astute amongst you might be wondering “if ruby goes through the chain again looking for :method_missing could we define our own method called :method_missing and have ruby run that instead?” and the answer is fuck yes we can. In fact, this is starting to stray into metaprogramming territory so I’ll quit while I’m ahead. Here is an example of me defining my own :method_missing method.

def skip.method_missing(name)
  "Oops, no method #{name}"
end
# => :method_missing
skip.meep
# => "Oops, no method meep"
skip.really?
# => "Oops, no method really?"

singleton class

In that last code block the def skip.method_missing syntax might’ve been new to you. In fact, just looking at it it’s not entirely clear where the method ends up being defined.

Based off what I’ve said so far and how ruby looks up methods, that method should live somewhere in the class or classes superclass, but a quick check in Person shows the method is nowhere to be seen.

Person.instance_methods(false)
# => [:speak]

I won’t go to the hassle of showing you but there is no method_missing defined anywhere in the chain I’ve described so far, except the original definition in BasicObject. So what’s the deal?

no more lies

I purposefully mislead you in the beginning, ruby doesn’t go right from an object into its class to look for a method. There is an extra step missing, ruby first goes right into the singleton class of an object, also known as the eigenclass and then goes up into the object’s class.

Previously when I used def skip.method_missing to define a new method, this syntax creates the method in the singleton class of the object. The singleton class is unique to the object in question, if I create a new Person they won’t have access to the method defined in skips’s singleton class.

alan = Person.new
# => #<Person:0x0000557383d470c8>
alan.meep
# => NoMethodError: undefined method `meep' for #<Person:0x0000557383d470c8>
skip.meep
# => "Oops, no method meep"

“Eigen” in german means “own”, so you can think of a singleton class (eigenclass) as a class that only the object in question has access to. It is its own class, completely separate from the object’s parent class.

The trick of overriding the method_missing is used a lot by Ruby on Rails and also by Hashie::Mash, to name but a few examples, in order to do some pretty cool things. This one little trick opens up a whole world of possibility for metaprogramming.

class methods

If you’ve worked with ruby at all then I’m here to tell you that you’ve been writing singleton methods all this time and you might not have even known it.

Remember when I said everything in ruby is an object? Well, that includes classes. Seeing as a class is an object we should also be able to define singleton methods on one.

def Person.hiya
  "Guten Tag"
end
# => :hiya
Person.hiya
# => "Guten Tag"

That to me looks an awful lot like a class method…in fact that’s precisely what it is. A class method is just a method that lives in a class’s singleton class. This revelation throws our current understanding of method lookup and my diagrams out of whack though, if you glance back you can see there isn’t anything to the right of a class for ruby to check inside.

more lies?!

Let’s extend the diagram from before to add some more clarity to this circus show of lies and deceit.

As you can see there is a little more to the picture than I was letting on. When you call a method on a class (which again is just an object) ruby still hops to the right to look into the singleton class of the class, the only difference then is that it will go up the chain looking in the singleton class of each superclass.

I’ll demonstrate by defining a method in the singleton class of Object.

def Object.another_method
  "hello from Object"
end
# => :another_method
Person.another_method
# => "hello from Object"

Start in Person and go right into the singleton class, then up the chain of singleton classes until you find the method you are looking for. This also means that the singleton class of BasicObject has a :missing_method function defined in there, so the same rules from earlier apply if a class method isn’t found.

demystifying syntax

You have probably seen class methods defined this way

class Person
  def self.sleep
    "zzz"
  end
end

Seeing as self inside a class references the class itself you can see that def self.sleep is exactly the same as def Person.sleep.

There is one other way you can define a singleton method on a class object:

class Person
  class << self
    def sleep
      "zzz"
    end
  end
end

What class << self is doing here is opening up the singleton class of the class you’re in so you can define methods in there instead of needing to define them on the object directly.

turtles all the way down

A singleton class is just that, a class. And as previously mentioned classes are objects, and what do objects have? Singleton classes. That means singleton classes have singleton classes, and that logic continues on down so singleton classes can have singleton classes that have singleton classes that have their own singleton classes and so on.

skip.singleton_class
# => #<Class:#<Person:0x00005573832d0db0>>
skip.singleton_class.singleton_class
# => #<Class:#<Class:#<Person:0x00005573832d0db0>>>
skip.singleton_class.singleton_class.singleton_class
# => #<Class:#<Class:#<Class:#<Person:0x00005573832d0db0>>>>

in a nut shell

Here is a summary of everything that I just said, and if you understand this then you are golden.

The superclass of the singleton class of an object is the object’s class. The superclass of a singleton class of a class is the singleton class of the class’s superclass.

but wait there’s more

Objects, classes, and singleton classes aren’t the only thing that you will find in the object model, there is also modules. Modules can be ‘prepended to’ or ‘included in’ a class. When you prepend a module it is inserted into the ancestor chain before the class, if you include a module it comes after the object.

module Thing;end
# => nil
Person.ancestors
# => [Person, Object, Kernel BasicObject]
Person.include Thing
# => [Person, Thing, Object, Kernel BasicObject]
module Stuff;end
# => nil
Person.prepend Stuff
# => [Stuff, Person, Thing, Object, Kernel BasicObject]

And here is a visual representation of that hierarchy

deliciously simple

My explanation in this post about the Ruby Object Model has been a simplified one, there are more complex diagrams out there which reveal some more fun information like every class’s class is Class (yes, even Class’s class is Class).

Person.class
# => Class
Person.class.class
# => Class
Person.class.superclass
# => Module
Person.class.superclass.class
# => Class
Person.class.superclass.superclass
# => Object

Here is one such diagram that I’ve found online

I hope that little journey through the Ruby Object Model has been helpful and eye opening for you. There is still more out there for you to learn but everything I’ve mentioned in this post will be more than enough for you to have a working knowledge of ruby’s object model.

prev: Shell Startup Files | next: Default Text Width @skipcloud