Dave's Ramblings
Dave's Ramblings

Dave's Ramblings


Blabberings and random thoughts from a Rubae grasping at a few languages.

Dave Allie
Author

Share


Include vs Prepend

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.

Dave AllieDave Allie

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 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?

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.

Include's Scenario

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's Scenario

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 leave a comment below.

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.

Dave Allie
Author

Dave Allie

Comments