Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samnang/1087802 to your computer and use it in GitHub Desktop.
Save samnang/1087802 to your computer and use it in GitHub Desktop.
require 'test/unit/assertions'
include Test::Unit::Assertions
assert_equal "1.9.2", RUBY_VERSION
# A Ruby meta-programming puzzle for polite programmers.
# This puzzle was created by Matt Wynne (@mattwynne) on 2011-04-10 inspired by Jim Weirich's
# talk at the Scottish Ruby Conference 2011.
#
# The challenge is: you have a class Foo which you want to monkey-patch, but politely.
# Here's the default behaviour of Foo
class Foo
def bar
"bar"
end
end
assert_equal "bar", Foo.new.bar
module Baz
def bar
"wee" + super
end
end
# # Unfortunately, if we try to monkey-patch Foo like this, it won't work
# class Foo
# include Baz
# end
#
# # this fails:
# assert_equal "weebar", Foo.new.bar
#
# This is because the include trick inserts the module Baz higher into the inheritance tree
# meaning Foo's own implementation of bar is hit first.
# So let's write a helper method that can do the patching for us, by extending each instance
# of Foo as it is created.
#
def extend_every(type_to_patch, module_to_apply)
type_to_patch.define_singleton_method(:new) do |*args, &block|
super(*args, &block).extend(module_to_apply)
end
end
extend_every(Foo, Baz)
assert_equal "weebar", Foo.new.bar
@mattwynne
Copy link

Interesting, I didn't know about that method.

Would this still work if you wanted to extend_every Foo with mutliple modules? Would they all be added, or just the last one?

@samnang
Copy link
Author

samnang commented Jul 19, 2011

The above solution will only apply the last one, but we could change the implementation into this for supporting multi levels:

require 'test/unit/assertions'
include Test::Unit::Assertions
assert_equal "1.9.2", RUBY_VERSION

# A Ruby meta-programming puzzle for polite programmers.

# This puzzle was created by Matt Wynne (@mattwynne) on 2011-04-10 inspired by Jim Weirich's
# talk at the Scottish Ruby Conference 2011.
#
# The challenge is: you have a class Foo which you want to monkey-patch, but politely.

# Here's the default behaviour of Foo
class Foo
  def bar
    "bar"
  end
end

assert_equal "bar", Foo.new.bar

module Baz
  def bar
    "wee" + super
  end
end

module Buzz
  def bar
    "buzz" + super
  end
end

# # Unfortunately, if we try to monkey-patch Foo like this, it won't work
# class Foo
#   include Baz
# end
#
# # this fails:
# assert_equal "weebar", Foo.new.bar
# 
# This is because the include trick inserts the module Baz higher into the inheritance tree
# meaning Foo's own implementation of bar is hit first.

# So let's write a helper method that can do the patching for us, by extending each instance
# of Foo as it is created.
#
def extend_every(type_to_patch, module_to_apply)
  type_to_patch.instance_eval do
    @__modules_to_apply ||= []
    @__modules_to_apply << module_to_apply

    def new(*args, &block)
      obj = super(*args, &block)
      @__modules_to_apply.each { |m| obj.extend(m) }

      obj
    end
  end
end

extend_every(Foo, Baz)
extend_every(Foo, Buzz)

assert_equal "buzzweebar", Foo.new.bar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment