First things first: Ruby is a dynamic language but it’s not dynamic in the same way Javascript is a dynamic language. You actually have real classes and class inheritance and not just prototypical inheritance. Also Ruby is truly object oriented—everything is an object, even classes, and there is no such thing as a distinction between value and reference types. In this article we will be looking at MRI (Matz’s Ruby Implementation) which is written in C to see how everything works behind the scenes. At MRI’s core there are two structures that describe the current list of objects in memory: RObject and RClass. The C code is a little bit complicated so I will present a simplified schematic version, which will allow you to understand the implementation.

At MRI’s core there are two structures that describe the current list of objects in memory: RObject and RClass. The C code is a little bit complicated so I will present a simplified schematic version, which will allow you to understand the implementation. Both structs inherit from another structure called RBasic. Here’s a simplified version of the internal representation of these objects (again, the real code is a bit more complicated):

image-title-here


You might have noticed a few things:

  1. Classes are also objects, like previously mentioned.

  2. Each object contains a collection of property values, just like Javascript objects.

  3. Unlike Javascript however, the methods are not implemented on the object level but rather at the class level — an object refers to a class, thus gaining access to the methods.

  4. The RClass struct has a super field which points to another class instance object which is the base class of the respective class.

As a side note, because a class is an object you can do something like:

The Class.new method accepts a block in which we define the class methods.

As you can see, Ruby’s dynamic nature translates into a very potent meta-programming ability. How much meta-programming you want to do and still have a fairly maintainable application is up for debate (and the subject of another article) however. For the time being, let’s look at how singleton methods / eigenclasses are implemented internally in the MRI.

Singleton Methods And Eigenclasses

You can also do something like:

…which defines a String instance and adds a method only to this instance.

But hold up, we discussed that we can only define methods on classes and those classes will be inherited by the object.

That’s correct. So what happens here is Ruby creates an eigenclass specifically for this object (which means “own class”—”eigen” means own or self in German). This class in our case is a child class of String. But we can go further—since you can add a method to any object, you can also add methods to class objects which turns out to be the closest thing Ruby has to static methods.

Let’s look at the following class definition:

And last but not least, you can also add methods to a class like this:

As you can see, Ruby is quite unique in that it’s dynamic, but it’s still class based (as opposed to objects being just property bags like in Javascript) and it’s quite peculiar in the way it’s implemented. And all these dynamic features makes it easy for us to do…

Metaprogramming

Metaprogramming is a rather loosely defined term, but the core concept is that the program is able to modify itself or generate new code on the fly. A very simple example of metaprogramming is defining accessors for properties in a class.

You don’t see that very often in practice however, what you do see is:

attr_accessor is a method defined of the Object class from which all Ruby classes inherit. It looks something like this:

If you are coming from C++, you might see the benefit in this particular example but might be scratching your head and wondering why is this desirable, because after all Ruby could have included a simplified way of accessing properties directly in its syntax. Well, the answer is Ruby prides itself on being a very expressive language and being conducive to developer happiness and what metaprogramming allows is a lot of functionality to be added on the fly depending on the state of specific objects. This reflects in the user friendliness of the libraries people write and one example of this would be ActiveRecord where a lot of functionality is generated on the fly.

Here’s an example straight from the AR docs. For the following table:

CREATE TABLE products (
   id int(11) NOT NULL auto_increment,
   name varchar(255),
   PRIMARY KEY  (id)
);

you can very easily access it like so:

It’s definitely a lot less verbose than if you would have written something similar in Java/C#, because the library takes care of a lot of magic for us. Ruby might seem like the perfect programming language but all this dynamism comes at a significant performance cost: Ruby (at least when using MRI) is two orders of magnitude slower than Node or Go.