The Real Use of Ruby Singleton Classes

Pascal Zumkehr

Most experienced Ruby developers have come across the concept of metaclasses or singleton classes. Still this seems to be something obscure and rarely used, even though we all benefit from singleton classes every day.

You might have read the classic articles on Ruby metaclasses by famous rubyists or by some lucky stiff. The mind-bending examples around  class << self; self; end made you doubt if this ruby language really is a sane thing. Still, you have learned that it is possible to define methods on a single object, not available on others of the same class:

Not that you ever thought about using such a thing, let alone having seen such usage in real life™.

Except that you do, all the time, without realizing it.

The Real Use of Singleton Classes

Let’s get the naming thing out of our way first: metaclasses, virtual classes or eigenclasses all refer to the same thing. Since Ruby 1.9.2,  Object has a method #singleton_class, and that is how I will call it for the rest of this post. The proximity to the Singleton Pattern is no coincidence, singleton classes do have exactly one „instance“.

We know that singleton classes somehow sit between an instance and its class, refining behavior defined in the class just for this very instance. In most projects, creating different classes for objects having different behavior does the trick. There is no need for those singleton classes.

We just forgot one little detail. You might have heard that all Ruby classes are objects too, inheriting from a class called Class. Look, for example, at the following ActiveRecord code:

Of course, we load the user with id 42 from the database. We do that every day. But look closer. Where is  .find defined? We do not need to know exactly, but surely somewhere around ActiveRecord::Base.

Recapitulate: Methods of an object are defined in its class, classes are objects themselves, the class of every class is Class . When calling User.find, Ruby should therefore look for the method  find in the class of User, which happens to be  Class – but  Class does not define any  .find method. Something is missing in our model.

By now you will have guessed it:  .find is defined in the singleton class of ActiveRecord::Base! This is exactly the use case for singleton classes: Even though every class directly inherits from Class, each may have its own methods, independent of others.

What we commonly call class methods, technically are (singleton) instance methods of class objects, defined in their respective singleton classes.

Furthermore, the singleton classes follow the same inheritance hierarchy as ordinary classes. The singleton class of  User inherits from the singleton class of ActiveRecord::Base. This is a great thing! Through that, the  .find method is available on the  User class object, allowing us to perform queries on the  users table, even though the implementation in  ActiveRecord::Base never knew anything about that table.

The Twofold Class Hierarchy in Ruby

It is time for a picture:

We see that each class in Ruby has a corresponding singleton class (which holds the so-called class methods). Both follow the same inheritance line, and both are instances of  Class (Go play around yourself in IRB with #class#singleton_class and  #superclass to figure out how the model continues from there).

This twofold hierarchy means that you can leverage the same inheritance potential for class methods as you can for instance methods, like method overriding or calling super. Generic class methods like  ActiveRecord::Base.find may be implemented in abstract super classes, returning results specific to the concrete class the method is eventually called on. I am sure there are various cases where you applied such class-side goodness but never though of the singleton classes and their parallel hierarchy involved.

As another example benefiting from this twofold class hierarchy, classes can act directly as their own factories:

The  Dialog does not need to know anything about the operating system. It can simply create a new instance based on the button class it gets, because  .create is inherited (as is .new , by the way).

Because the whole singleton class concept originates in Object, it is not only applicable to classes but also to regular objects. This enables the initial  matz.speak example and is shown in the lower left corner in the diagram above. Compared to the meaning of singleton classes for class methods, this is a mere side-effect. In over ten years of Ruby projects, we had one (!) single use case for an object singleton class, but benefit from the twofold class inheritance all the time.

P.S.: If you are looking for even more mind-bending academic discussions of singleton classes and their complete internals, make sure not to miss this Wikipedia article.

Kommentare sind geschlossen.