Forked from mattwynne/a_ruby_metaprogramming_puzzle_for_polite_programmers.rb
Created
April 10, 2011 17:02
-
-
Save lazyatom/912526 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
patcher = Module.new do | |
# Use define method to get closure-type behaviour within the method "body" | |
define_method(:new) do |*args| | |
super(*args).extend(module_to_apply) | |
end | |
end | |
type_to_patch.class_eval do | |
extend patcher | |
end | |
end | |
extend_every(Foo, Baz) | |
assert_equal "weebar", Foo.new.bar # => works. |
I think you'd want to do:
define_method(:new) do |*args, &block| super(*args, &block).extend(module_to_apply) end
It doesn't matter in this case (since Foo.new
does not expect a block), but if it did, this would be important.
Yep, that's a good point.
Here's my revision: https://gist.github.com/1087802
I make it into shorter by using define_singleton_method
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I did not know you could do *args to a block like that. Ruby 1.9 FTW!