Ruby superpowers — with great power comes a great prepend

Ricardo Brazão
Onfido Product and Tech
4 min readFeb 15, 2017

--

You were walking into a science experiments lab, when suddenly you’re bitten by a radioactive spider. The next day you wake up feeling weird until you realised you’re given superpowers by that bite. You could control the way you position yourself in your hierarchical family tree! So you could make yourself your father’s father (basically your grandfather…), isn’t that amazing?!?

Well that’s not a real superpower, but that’s what happens when you use the ruby prepend method when handling a modules.

Wait…

One of the first things that we learn in ruby is that everything inherits from Object , and that’s totally true but the caveat here is what lies between our class and Object . Lets see the following code:

class SomeClass
end
SomeClass.ancestors
=> [SomeClass, Object, Kernel, BasicObject]

With this example we prove that our previous assumption was right. But what will happen when we add a module to that class?

module SomeModule
end
class SomeClass
include SomeModule
end

Will the hierarchy change? Where will the module be? So this is how the hierarchy looks like:

SomeClass.ancestors
=> [SomeClass, SomeModule, Object, Kernel, BasicObject]

As you can see SomeModule will be placed between the class and the Object. This is all good until you have some requirements for that module that include doing something before the class, or even surround a call to a function in the class with some behaviour from the module.

Wait, I’m here to read about the prepend why are you still writing about the include? Well, we’ll get to the prepend in a moment, but for you to understand in what scenarios the prepend comes in hard for the kill, I’ll show you how things were done before it appeared.

Say we have the following class:

class George
def talk
"I'm George."
end
end

To make George talk (without having to wire him to a car battery 😱) we just need to call George.new.talk and it would talk back just I’m George.. This is all good but it’s not very polite, is it? Lets create a module that will bring some politeness into this world.

module Politizer
def talk
"Hello. #{super}. How are you today?"
end
end

Now lets make George more polite.

# Making George politeGeorge.include(Politizer)george = George.newgeorge.talk # => "I'm George."

But…but…I’ve made you more polite with my Politizer , what happened in here?! Well, as you must’ve guessed, we are not reaching the talk method that we redefine in the module due to it’s hierarchy:

=> [George, Politizer, Object, Kernel, BasicObject]

So what can we do if the include will always add the module’s methods behind the class itself? To achieve that, we’ll need to use the alias_method method with a little magic.

Modules have a callback that is called when they are included, so we will work with that to achieve great things! That said, lets change our Politizer and make George polite.

module Politizer
def self.included(base)
base.class_eval do
alias_method :talk_without_polite, :talk
alias_method :talk, :talk_with_polite
end
end

def talk_with_polite
"Hello. #{talk_without_polite}. How are you today?"
end
end
# Making George politeGeorge.include(Politizer)george = George.newgeorge.talk # => "Hello. I'm George. How are you today?"

YEAH! George is now a polite fellow, but that code…damnnn it looks ugly as hell! That is why for these type of situations, in the Rails world, we have a alias_method_chain method that makes things a liiiiiiiiitttle bit better:

class George
def talk
"I'm George"
end
end
module Politizer
def self.included(base)
base.class_eval do
alias_method_chain :talk, :polite
end
end
def talk_with_polite
"Hello. #{talk_without_polite}. How are you today?"
end
end
# Making George politeGeorge.include(Politizer)george = George.newgeorge.talk # => "Hello. I'm George. How are you today?"

But now lets get down to business and talk about the thing you’re here for: the prepend!! So, in ruby 2.0 (yes, I’m a little late to the hype train…) the prepend method was introduced as the saviour for all of the trouble we had to make George more polite.

With that in mind this is how we could add the Politizer module to George to achieve the behaviour that we want:

class George
def talk
"I'm George"
end
end
module Politizer
def talk
"Hello. #{super}. How are you today?"
end
end
# For ruby 2.0
George.send(:prepend, Politizer)
# For ruby 2.1
George.prepend(Politizer)
# Making George polite
george = George.new
george.talk

This code is cleaner and more understandable than the previous one, with the include magic, right? So you must be wondering how this works, and what is the difference between include and prepend . The diference is…drum roll…object hierarchy! This is what object hierarchy with the prepend method looks like:

George.ancestors
=> [Politizer, George, Object, Kernel, BasicObject]
# George with include
# => [George, Politizer, Object, Kernel, BasicObject]

You see the diferences, right? So now when we call george.talk we are looking first in the module instead of the class itself, and with the wonders of hierarchies we can call super to call the methods on the parent class itself.

As usual if you find this useful smash that 💚 button, and if you have anything to say just leave a comment or drop it like it’s hot @RicBrazao!

--

--

Ricardo Brazão
Onfido Product and Tech

Web Developer — Basketball enthusiastic — Game consumer