← archive

Overriding instance method with a module

August 6, 2012
ruby

This is the problem I come across every now and then, so I think a little write up explaining what's happening could help me understand it better.

Let me describe the problem:

module CanCook
  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class Programmer
  include CanCook
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I don't know how to bake tortillas :(

Poor Mario doesn't know how to bake anything. We want to teach him how to bake, to stand out from other programmers!

The problem is described in the following diagram made by Gavin Kistner:

There's a post on StackOverflow where Gavin presents workarounds for this. I'm going to explain them and add some more:

Subclass Programmer and then include module

module CanCook
  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class CulinaryProgrammer < Programmer
  include CanCook
end

Mario = CulinaryProgrammer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

What's wrong with this?

This isn't a real world example, just a simple one that describes this behavior. This would rarely fit you, since you now have to create instances of a new class that will fit your needs. In the real world, you would want instances of the original class to behave like this.

Extend just the instances you need

module CanCook
  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

Mario = Programmer.new
Mario.extend CanCook
Mario.bake("tortillas") #=> I'm baking the tortillas!

What's wrong with this?

This is better than the previous solution, it skips the part of inheriting the original class, but you still have to extend each instance. What happens with already used instances in your application?

Perform a gross hack that removes the original method

module CanCook
  def self.included(base)
    base.class_eval do
      remove_method :bake
    end
  end

  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class Programmer
  include CanCook
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

What's wrong with this?

Just as Gavin describes it in his post, this strategy is invasive. Your old method will be deleted and you will not going to be able to use it afterwards. This is the best solution so far, even though remove_method seems dirty. The good part is that your instances of Programmer will now know how to bake things automatically. Hooray!

No need to use module if using remove_method

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class Programmer
  alias_method :_real_bake, :bake
  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

What's wrong with this?

This post describes modules overriding instance methods, that's what's wrong. :) It makes sense to not use the module, though. The original method is preserved and available for later usage. However, this is not that clean. Modules are used to describe behavior that can be shared between objects. This removes that flexibility.

Here's the alternative:

module CanCook
  def override_bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class Programmer
  include CanCook
  alias_method :bake, :override_bake
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

I like this more since it still preserves the original, but we're still using a module, even though the methods don't share the name.

Use (prepend) library by John Mair

require "prepend"
module CanCook
  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class Programmer
  prepend CanCook
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

What's wrong with this?

It's depending on external library.

The ultimate solution of them all

These are all the solutions presented in that SO post, but we haven't yet found the ultimate one. Does any of these make you jump around your room screaming how great this code is? I didn't think so.

But behold! This is the solution by @rkh which pretty much encapsulates why I love his work.

module Prepending
  def append_features(base)
    prepend = self
    base.extend Module.new { define_method(:new) { |*args,&block| super(*args,&block).extend(prepend) }}
  end
end

module CanCook

  extend Prepending

  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

class Programmer
  include CanCook
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

I'm going to describe this solution in detail, so if you're not amazed, this post will not get any more interesting.

So where to begin?

  • Any external dependencies? No.
  • Are we getting invasive on original method? No.
  • Do we have to do something special with the instances we would like to have the method overridden? Not at all.
  • Is this code awesome? Hell yeah!

Isn't this what you first think of when you wish to override instance method with a module?

Okay, enough with the praising, let's start explaining! Our code is nicely organised, but the whole beauty lays in a module named Prepending. All logic needed to prepend instance methods is located there.

From ruby documentation:

append_features(p1)

When this module is included in another, Ruby calls append_features in this module, passing it the receiving module in mod.

Documentation is somewhat confusing. This method will get called even when the module is included in a class, since Class inherits from Module. In our code it happens when we include CanCook into Programmer on line 24.

Note that self will refer to CanCook, and base will refer to Programmer class.

    prepend = self

Save this module to a variable, so we can use it later.

base.extend Module.new { define_method(:new) { |*args,&block| super(*args,&block).extend(prepend) }}

Extend the Programmer class with anonymous module

This is somewhat more expressive:

module Prepending
  def append_features(base)
    prepend = self
    beauty = Module.new do
      define_method(:new) do |*args,&block|
        super(*args,&block).extend(prepend)
      end
    end
    base.extend beauty
  end
end

Why are we using anonymous module here?

By defining the module and the method in block, we stay in the same lexical closure, so we can access the local variables.

What is wrong with this line of code?

module Prepending
  def append_features(base)
    prepend = self
    beauty = Module.new do
      def new(*args,&block)
        super(*args,&block).extend(prepend)
      end
    end
    base.extend beauty
  end
end

prepend doesn't exist in the context of the new method, since it's a local variable. We have to define a method dynamically to make it available by preserving original context.

Okay, let's explain the code inside this dynamically created method. It's called new and it overrides a Programmer's constructor, so when Programmer.new is called, this method will get called instead.

It calls the original constructor with super, which returns a new instance of Programmer and then extends it dynamically with itself (this module, or the module that extends this module). This enables overriding instance methods.

Update

Thank you all for discussing this post and sharing your opinions.

Ryan LeCompte has another, more easily understandable solution. Instead of using an anonymous module, we could simply use definesingletonmethod.

module Prepending
  def append_features(base)
    prepend = self
    base.define_singleton_method(:new) do |*args, &block|
      super(*args, &block).extend(prepend)
    end
  end
end

module CanCook

  extend Prepending

  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end


class Programmer
  include CanCook
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

As Markus explains, we could have extended the instance directly in a constructor.

module CanCook
  def bake(food_name)
    "I'm baking the #{food_name}!"
  end
end

class Programmer
  def initialize
    self.extend CanCook
  end

  def bake(food_name)
    "I don't know how to bake #{food_name} :("
  end
end

Mario = Programmer.new
Mario.bake("tortillas") #=> I'm baking the tortillas!

Florian Hanke explains that this code doesn't express its intent very well and I couldn't agree more with him. I was very excited when I first saw Konstantin Haase's technique of achieving this behavior (here is the original gist). I've written this post with the intent of describing that interesting technique. I didn't analyse possible future usages.

You should also read an interesting post by Josh Cheek about dangers of using hooks.

Finally, thanks to Nikica for a suggestion about lexical context.

Want to talk more about this or any other topic? Email me. I welcome every email.