Singleton classes (briefly...)
This was the tip of the week in the August 19, 2021 Ruby Weekly Newsletter.
In the past few tips, we’ve covered ancestors, prepend
and include
, all in service of learning different ways Ruby allows us to import code into a class. There is one more way to do this, with extend
. However, before we jump straight into learning about extend
, we first need to understand singleton classes.
Singleton classes are key to Ruby’s object model. Let’s say we have an instance of a class, and want to define a method on just that instance:
class ExampleClass ; end
example = ExampleClass.new
def example.example_method
"This is a singleton method"
end
example.example_method
=> "This is a singleton method"
Example.new.example_method
# NoMethodError (undefined method `example_method' for #<ExampleClass:0x00007f9686858a80>)
We see that our example
instance can have its own specially defined method, example_method
, which won’t work on other instances of ExampleClass
. Where is example_method
stored though? How does Ruby keep track of it? It’s defined on example
’s singleton class.
Every Ruby object has a singleton class. This allows us to do things like the above: define methods on instances and have them work only for those instances. We also know that classes in Ruby are themselves objects - and so also have singleton classes. This is actually how class methods work under the hood!
Class methods are really instance methods on a class’ singleton class. Let’s look more deeply:
class ExampleClass
def self.example_class_method
"This is a class method"
end
end
ExampleClass.example_class_method
=> "This is a class method"
ExampleClass.singleton_class.instance_methods(false)
=> [:example_class_method]
Singleton classes are deep enough to deserve a whole series themselves! For now though, we’ve learned what we need in order to properly understand how extend
works next week. Namely, we’ve learned that class methods are instance methods on the singleton class of a class.