The singleton class, also referred to as the metaclass or the eigenclass. What is it exactly?
Let's begin by discussing the main purpose of metaclasses in Ruby.
Classes and Method Dispatching
In Ruby, everything is an object, and every object has a class, which defines the methods the object can respond to.
These two statements also apply to classes, which is what makes it possible for classes in Ruby to have their own instance variables and to receive methods.
class Animal
@description = 'A multicellular eukaryotic organism.'
def self.description
@description
end
end
Animal.description # => "A multicellular eukaryotic organism."
In contrast with other languages, in Ruby what we usually refer to as class methods are simply instance methods of a class object.
Now, if a class in Ruby is an object, and every object has a class which defines its methods, then a class in Ruby has a class which defines its methods.
Animal.class # => Class
If Class
defined those methods, then they would be available for any class object.
Integer.description
# NoMethodError (undefined method `description' for Integer:Class)
How does Ruby pull it off then?
The Metaclass of a Class
Ruby deals with it by giving every object its own unique class, which defines the methods available for that object. Its very own metaclass.
The metaclass is called the singleton class because there is a single instance of it.
Animal.singleton_class # => #<Class:Animal>
This class is hidden in the inheritance chain (it doesn't show up when calling ancestors
, and class
returns Class
), but we can think of it as the first ancestor when it comes to dispatching methods.
When calling a method on an object, Ruby will perform the method lookup by first checking on the object's metaclass, before traversing the rest of the method chain.
Animal.singleton_methods # => [:description]
Just like instance methods are defined in a class, class methods are defined in the metaclass of a class object.
This design enables Ruby to share the same dispatch mechanism for class methods: internally all methods are instance methods.
The Metaclass Hierarchy
In the diagram below we can see that each class has a corresponding metaclass, which is where their respective class methods are defined.
The inheritance chain of metaclasses is what allows to inherit and override class methods and call
super
.
And that is the main purpose of metaclasses in Ruby.
The Metaclass of an Object
Notice how the instance also has its own metaclass: classes are just a particular kind of object. When we call a method on an instance, Ruby will also look in its metaclass first.
Let's see a few examples of how we can use the metaclass to define methods that are specific to a particular instance.
animal = Animal.new
other_animal = Animal.new
class << animal
def bark
'Woof'
end
end
def animal.greet
'hi'
end
animal.define_singleton_method(:roar) { 'Rawr!' }
animal.bark # => "Woof"
animal.greet # => "Hi"
animal.roar # => "Rawr!"
animal.singleton_methods # => [:bark, :roar, :greet]
animal.singleton_class.instance_methods(false) # => [:bark, :roar, :greet]
other_animal.bark # NoMethodError (undefined method `bark' for #<Animal>)
other_animal.singleton_methods # => []
You will certainly recognize the first two patterns, they use the same syntax that is typically used with self
to define class methods!
Once again, classes are just a particular kind of object. And yet, unlike classes, modifying the behavior of a particular object is rarely used in practice.
In the next post I share a few useful applications of this feature, read on!