Include and prepend are extremely useful tools for adding and modifying logic in multiple places at once, or even just
splitting a file into components. Let’s have a look at what they’re doing behind the scenes.
In order to explain the differences, advantages, and disadvantages of include and prepend, there are a couple of
concepts that we’ll need to cover first.
In Ruby, every class has an ancestry chain. The most basic way to construct that chain is through subclassing:
class A
def foo
'foo'
end
def bar
'bar'
end
end
class B < A
def foo
super + 'baz'
end
end
B.ancestors
# => [B, A, Object, Kernel, BasicObject]
In the example above, the chain starts with B
, then moves up to A
and then through the superclasses of A
.
The ancestry chain is instrumental in finding the correct method block to execute when a method is called. Calling a
method traverses the object’s ancestry chain searching each object along the way for a method with the same signature as
the one called.
In the example above, when we call B.new.bar
, B
is searched for an instance method named bar
, which is not found.
So we look along the chain one position, at A
, which does contain our instance method so we stop looking. A
’s bar
is executed on the B
object we created, returning ’foo’
.
When we call B.new.foo
, B
is searched for foo
, which is found, so the searching stops. However, when executing
B
’s foo
, super
is called. super
starts searching again for the foo
method, but this time starting at B
’s
first parent, A
. Luckly, A
contains foo
, so we can stop our search. A
’s foo
is run (returning ’foo’
) and
added to ’baz’
, so B
’s foo
returns ’foobaz’
.
Pretty easy right? So what are include and prepend useful for?
Including a module into a class is like giving the class another superclass. The methods in the module act like methods
from a superclass, except included modules will be inserted before any actual superclasses in the ancestry chain.
Prepending a module to a class is like redefining the class to be a subclass of itself, where the only methods in the
subclass are the module’s methods, and the original class’s methods are like superclass methods. These examples should
make it a little more clear:
module Ext
end
class Example
include Ext
end
Example.ancestors
# => [Example, Ext, Object, Kernel, BasicObject]
module Ext
end
class Example
prepend Ext
end
Example.ancestors
# => [Ext, Example, Object, Kernel, BasicObject]
In the first example, the Ext
module was included in Example
and was inserted after Example
in the ancestry
chain (much like a superclass). While in the second example, the Ext
module was prepended to Example
as was inserted
before Example
in the ancestry chain (much like a subclass, only the bottom of the chain isn’t the name of the
current object).
Both include and prepend have their uses in the real world, so let’s create a scenario for each and explore both of
them.
A common use case for include, is when we have a method and associated logic that we want to define in a single file but
use in multiple classes. However, in one of those classes, we want to override one of the methods we defined in our
module.
module Ext
def foo
'do something'
end
end
class Class1
include Ext
end
class Class2
include Ext
def foo
super + ' else'
end
end
Class1.new.foo
# => 'do something'
Class2.new.foo
# => 'do something else'
By using include, Ext
is inserted after the each class in their respective ancestry chains. This allows us to call
super
in Class2
’s foo
and get access to Ext
’s foo
.
Prepend is much less commonly used, but it’s useful if we are using someone else’s library and need to override the
library’s default behaviour.
class ExternalClass # pls no touch
def foo
'I am me'
end
end
module Ext
def foo
super + ', or am I?'
end
end
ExternalClass.prepend(Ext)
ExternalClass.new.foo
# => 'I am me, or am I?'
By using prepend, Ext
is inserted before the external class. This allows us to call super
in our custom module
and get access to ExternalClass
’s method.
Both of these use cases have, and will continue to, come up in the real world time and time again. Now that you know a
more what include
and prepend
do behind the scenes, go out into the wild and include
or prepend
a little
smarter!
If you think I’ve made a mistake/am completely and utterly wrong/just want to provide some feedback, please reach out.
TL;DR: Use prepend
when overwriting/extending an existing class. Use include
when creating a small snippet of
logic that classes can include and override if need be.