Prepend in Ruby

Eugene Nikolaev
3 min readJan 23, 2023

--

There are three ways to include modules in Ruby: include, extend and prepend. While first two are commonly used everywhere, but we don’t see prepend used often. Actually, I can’t remember any case in my career where I’ve seen that method. I’ve been interviewing many people recently and even on senior positions people don’t seem to know what it actually does. So, in this article I am going to explain its functionality and give as many more or less real world use cases as I can.

First of all, let me explain what it does. All methods above include module to inheritance chain of a Ruby class. Include and extend add method declared in the module up in inheritance chain and normally used as mixins to reuse code. Difference between those two is whether to add module method to class or to instance.

module UseMe
def foo
p 'hello'
end
end

class MyClassOne
include UseMe
end

# MyClassOne.new.foo
# => hello

class MyClassTwo
extend UseMe
end

# MyClassTwo.foo
# => hello

Inheritance chain goes like this:

p MyClassOne.ancestors # same for MyClassTwo
# => [MyClassOne, UseMe, Object, Kernel, BasicObject]

Prepend adds module method down into inheritance chain, which means that method from module will actually executed instead of method, defined in class.

class MyClassThree
prepend UseMe
end

MyClassThree.new.foo
# => hello
p MyClassThree.ancestors
# => [UseMe, MyClassThree, Object, Kernel, BasicObject]

In the example above we used prepend the same way as the other two, and seemingly there is no difference yet. But the fun starts when we add method into class with the same name.

module UseMe
def foo
p 'hello'
end
end

class MyClassThree
prepend UseMe

def foo
p 'I think I am being ignored here'
end
end

MyClassThree.new.foo
# => hello

Because UseMe is now lower in inheritance chain it will be executed instead of class method, which will be completely ignored until we add super() to module method.

module UseMe
def foo
p 'hello'
super() # using of brackets is because of Ruby style guide
end
end

class MyClassThree
prepend UseMe

def foo
p 'Now I am executed'
end
end

MyClassThree.new.foo
# => hello
# => Now I am executed

Real world prepend use cases

Disclaimer: use of prepend sometimes can cause too much implicit behavior in code, be careful to not confuse other developers.

Use case #1: Logging

module Loggable
def create_user(*args)
Rails.logger.debug "Creating user with params #{args}"
super(*args)
Rails.logger.debug "User created"
end
end

class UserService
prepend Loggable

def create_user(email, name)
User.create(email: email, name: name)
end
end

Here we have rails-ish app as an example and if we want to log certain methods without touching actual methods, we can prepend Loggable module and magic will do work.

Use case #2: Transaction

module Transactionable
def create_user(*args)
ActiveRecord::Base.transaction do
super(*args)
rescue
Rails.logger.error "Transaction for creating users failed"
end
end
end

We can also wrap methods in transactions and maybe add some transaction related logic in prepended module.

Use case #3: Debugging

module Debuggable
def do_something(*args)
time_start = Time.now
super(*args)
time_end = Time.now
p "Time spent: #{time_end - time_start}"
end
end

Regarding cases above, there might be a good question: do we actually need to declare each method class uses in modules like Loggable? That would basically mean that we won’t be able to reuse module for anything that doesn’t contain same methods as the class it was included in.

I have weird looking but working solution for that as well.

module Loggable
def initialize
self.class.ancestors[1].instance_methods(false).each do |method_name|
Loggable.define_method(method_name) do
super()
p "Method #{method_name} was invoked"
end
end
end
end

class UserService
prepend Loggable
def create_user
p 'creating user'
end
end

With some touch on meta programming, now we have prepended versions of methods for any method define in class.

So, while prepend is much less intuitive than its siblings and in some cases can lead into implicit ‘too magical’ approach, it’s pretty solid tool which can perfectly fit into some cases, so let’s not forget about this nice Ruby feature.

--

--

Eugene Nikolaev
Eugene Nikolaev

Written by Eugene Nikolaev

0 Followers

Senior Software Engineer at GMO GlobalSign

Responses (1)