/dev/blog

because /dev/null sucks so bad

overriding instance method with module

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:

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

Why I don’t like it

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

Why I don’t like it

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

Why I don’t like it

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, eventhough 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

Why I don’t like it

Because this post describes modules overriding instance methods :) 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. Rubyists use modules to describe behavior that can be shared between objects. This removes that flexibility.

Here’s the alternative:

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

  • Use (prepend) library by John Mair

Why I don’t like it

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? Yeah, me neither.

Until now!

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

I’m going to describe this solution in detail, so if you’re not amazed with this one, don’t waste any more of your time on this post.

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 overriden? 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 Prepending module. All logic needed to prepend instance methods is located there.

  • append_features

From ruby documentation:

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 26.

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.

  • The bestest line eva!
1
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:

1
2
3
4
5
6
7
8
9
10
11
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?

1
2
3
4
5
6
7
8
9
10
11
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 use it.

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.

awesome

UPDATE

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

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

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

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.

Comments