Assigning methods to specific objects and not their classes in Ruby
TweetOn most days when you use Ruby, you write a class and bestow upon it some methods, then instantiate it and use its methods:
class Crossfield < FederationShip
def goto(location)
puts "Warping to #{location}"
end
end
crossfield = Crossfield.new
crossfield.goto(:starbase_9)
# Warping to starbase_9
Everything is going as it should be, until one day, you decide an instance is going to be special. It will walk differently.
discovery = Crossfield.new
def discovery.goto(location)
puts "Spore Jump to #{location}"
end
Wait you can do that? What happens if I call goto
now?
crossfield.goto(:starbase_9)
# Warping to starbase_9
discovery.goto(:starbase_9)
# Spore Jump to starbase_9
What just happened there?
On the surface it seems like you have just modified an instance to return a different method. But wait, aren’t methods only defined on classes? This isn’t JavaScript where classes and instances are just objects that quack right?
If you were thinking that, you are right. Methods in Ruby can only be defined on classes, and they can only be called when bound to an instance. What just happened was that we defined a method on a Singleton Class
What is a Singleton Class?
In Ruby, every object comes with a Singleton Class. Not to be confused with Singletons which refer to the Singleton Pattern, Singleton Classes are special classes that accompany every object in Ruby.
# Extracting the Singleton Class
discovery.singleton_class
=> #<Class:#<Crossfield:0x00007f92...>>
Singleton Classes inherit from the class that an object is an instance of, so any methods defined on it are available on the instance, as with normal class inheritance rules:
discovery.singleton_class.ancestors
=> [
#<Class:#<Crossfield:0x00007f92...>>,
Crossfield,
FederationShip,
Object,
Kernel,
BasicObject
]
Singleton Classes however are specific to a particular instance, so modifying such a Singleton Class only modifies that instance’s Singleton Class.
# Syntax to modify an instance's Singleton Class
class << discovery
def grow_spores
puts 'Growing Spores'
end
end
discovery.grow_spores
# Growing Spores
Looks familiar? That is because it is basically similar to defining a Class Method.
Class methods
You might now be thinking, so wait a minute, if we defined instance methods on a class, what are class methods? Well, in Ruby everything is an Object, so Classes are Objects too. They are instances of the Class
class.
That means that every class has their own Singleton Class too.
The class Class is also an Object, and therefore also has a singleton class too.
This is how you would have seen people define class methods:
# How we normally define class methods:
class FederationShip
def self.prefix
'NCC'
end
end
FederationShip.prefix
=> "NCC"
This is another way you would have seen a class method being defined:
class FederationShip
class << self
def prefix
'NCC'
end
end
end
You can also use it in this way:
class << FederationShip
def prefix
'NCC'
end
end
Or even just:
def FederationShip.prefix
'NCC'
end
Essentially, the class << FederationShip
syntax is syntactically equivalent to:
FederationShip.singleton_class.class_eval do
# inside
end
In fact, you can retrieve the Singleton Class in that manner:
# This was the way you had to extract the Singleton Class before the introduction of the singleton_class method.
class << FederationShip
self
end
=> #<Class:FederationShip>
A Singleton Class of a class, unlike the Singleton Class of an object, does not have parents that are related to the class itself, because the class isn’t an instance of itself:
Crossfield.singleton_class.ancestors
=> [
#<Class:Crossfield>,
#<Class:FederationShip>,
#<Class:Object>,
#<Class:BasicObject>,
Class,
Module,
Object,
Kernel,
BasicObject
]
It does however, inherit from the singleton classes of its parents, which include the object, which means any methods that were defined on its parents would be accessible on it too. For instance, the prefix
method defined earlier on FederationShip
would also be accessible on Crossfield
, a child class:
class << FederationShip
def prefix
'NCC'
end
end
class Crossfield < FederationShip
end
Crossfield.prefix
=> "NCC"
So, if a class was an object that had a singleton class, does a singleton class have a singleton class too?
Why yes!
Crossfield.singleton_class.singleton_class
=> #<Class:#<Class:Crossfield>>
And these are its ancestors:
Crossfield.singleton_class.singleton_class.ancestors
=> [
#<Class:#<Class:Crossfield>>,
#<Class:#<Class:FederationShip>>,
#<Class:#<Class:Object>>,
#<Class:#<Class:BasicObject>>,
#<Class:Class>,
#<Class:Module>,
#<Class:Object>,
#<Class:BasicObject>,
Class,
Module, Object, Kernel, BasicObject]
This is quite a bit to unpack here, but essentially, since a singleton class of a singleton class contains the methods available to singleton classes, the singleton class neccessarily inherits from the singleton class of the class Class
itself!
Which means defining methods on the Class class essentially defines it on every class, which is what we expect.
class Class
def meow
'meow'
end
end
class A
end
A.meow
=> 'meow'
Back to the discovery
instance, the class <<
syntax can also be used on instances since instances are no different from classes:
# This does exactly the same thing as what we did right at the top of this post.
class << discovery
def goto(location)
puts "Spore Jump to #{location}"
end
end
Which is probably cleaner syntax if you wanted to only operate on particular instances at a time.
Hopefully this post would have helped explain how Ruby allows you to define class and instance methods, and how both kinds of methods are essentially the same thing, but on different singleton classes that inherit from different singleton classes.
If you want to learn more about this topic, Metaprogramming Ruby is a great read that goes into more detail.
We are giving out a Ruby Cheatsheet
Many people use Ruby just fine not knowing the intricacies of Singleton Classes, but while working on our Ruby apps we have discovered over the course of writing Ruby code that it has a lot of useful features.
Some of it is less well known than others, and are extremely useful in specific places. We have attempted to compile these, including info about Singleton Classes and more into a small booklet called The Ruby Cheatsheet that we will be handing out at RubyKaigi 2019!
Come over and have a chat at our booth on the fifth floor if you are there!