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:
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.
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?
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!
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.
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.
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?
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) }}
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.
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.