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.
Ancestry Chain in Ruby
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
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 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.
bar is executed on the
B object we created, returning
When we call
B is searched for
foo, which is found, so the searching stops. However, when executing
super is called.
super starts searching again for the
foo method, but this time starting at
B's first parent,
foo, so we can stop our search.
foo is run (returning
'foo') and added to
Pretty easy right? So what are include and prepend useful for?
Include vs Prepend
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
foo and get access to
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
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
prepend do behind the scenes, go out into the wild and
prepend a little smarter!
If you think I've made a mistake/am completely and utterly wrong/just want to provide some feedback, please leave a comment below.
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.