Singleton Methods in Ruby

Tyler Brewer
4 min readJan 30, 2018

--

Anytime a developer says “Maybe this situation is a good use case for a singleton,” you’ll hear either shrieks or cheers depending on the past experience of the people in the room. “Singleton” can be a trigger word because if the “singleton” design pattern is implemented incorrectly or used in a situation that might not be the best fit, it can be a pain to deal with. Fortunately, I’m not going to be writing about the “singleton” you’re most likely thinking of. Instead, this post is going to be about the lesser known ability in Ruby, the singleton method.

Let’s say we have a Bicycle class that takes a model, color and diameter using keyword arguments (you are using keyword arguments, right?). Each instance will also have a method called wheel_radius that will use the diameter to find the radius of the wheel. This class might look something like this:

class Bicycle
attr_accessor :make, :color, :diameter
def initialize(model:, color:, diameter:)
@model = model
@color = color
@diameter = diameter
end
def wheel_radius
diameter / 2
end
end

This works great, and we can use it to create millions of bicycles and find their radiuses:

canyon = Bicycle.new(
model: 'Merckx',
color: 'black',
diameter: '4'
)
canyon.radius # 2

Now, maybe a few years from now we decide we want to try something new. We decide that instead of a new bike with circular wheels, we decide to create one with square wheels. We’re tired of the ease of cycling and think having square wheels will improve our quality of life and make us have larger calves. So, in our pursuit of happiness, we manufacture our bike:

The masochist’s dream bike

To our great surprise, we hate it. But, there are some masochists in this world who love it and refuse to ride anything else, so we have to handle this type of bike in our code. We could potentially solve this using singleton methods.

Let’s say we want to find the area of the square wheeled bike using a wheel_area method. Adding this to the Bicycle class’s interface might confuse developers using our code later since all other instances of Bicycle will not need this functionality at all. Instead, we can add a singleton method to our Bicycle instance with square wheels. By doing this, we add functionality that is specific to a particular instance.

Let’s go ahead and instantiate our bikes:

normal_bike = Bicycle.new
model: 'Merckx',
color: 'black',
diameter: '4'
)
square_bike = Bicycle.new(
model: 'Genius',
color: 'neon',
diameter: 4
)

Now that we have the bike instantiated, we can now add the functionality specific to our instance using a singleton_method:

def square_bike.wheel_area
diameter ** 2
end
square_bike.wheel_area # 16
normal_bike.wheel_area # this will throw undefined method wheel_area

If you’ve never seen this before, it probably looks very weird. We just modified the functionality of our specific instance at run time. Another syntax for adding singleton methods to an instance might look something like this:

class << square_bike
def wheel_area
diameter ** 2
end
def reason_for_riding
'To cause pain and misfortune'
end
end

This might be a better use case if you are adding multiple singleton methods to an instance because if your variable containing your instance ever changes names, you only need to change it in one spot (also, it’s a lot easier to type).

So how does this all work? Before we can tackle this, we need to understnad how Ruby knows where to find a method when it is called, also called method lookup. For the sake of time, this is a great gist explaining method lookup.

When we call a method on an instance, Ruby first looks in a lesser known singleton class that’s created with each instance. This is the absolute first place Ruby ever looks to call a method, so anything in this instance will overwrite the methods in the regular class (in our case Bicycle).

Ruby will always first look in an instances singleton class for methods

We can also see this inheritance chain if we call singleton_class.ancestors on our instance:

square_bike.singleton_class.ancestors
# <Class:#<Bicycle:0x0055f3047e10b0>>, Bicycle, and so on

Hopefully it’s pretty clear how powerful the use of this singleton class created for each instance can be. It’s also crucial to point out that this should be used very sparingly. If you’ve been a Ruby developer for any amount of time, you’ve probably been bitten by it’s ability to allow you to manipulate basically any object at any time. If you do use this, seriously consider how you can prevent any headaches for developers that may be using your class down the road. With that said, this can be a quick and simple way to get yourself out of a bind if the situation arises.

--

--