Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save samnang/1087802 to your computer and use it in GitHub Desktop.

Select an option

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
@samnang
Copy link
Copy Markdown
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