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):
You might have noticed a few things:
Classes are also objects, like previously mentioned.
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:
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.