I’m having a go at metaprogramming in ruby. It started out simply enough. I wanted a module which had some custom assert methods in. Here’s the test:
def test_matches
assert_stderr_matches(/foo/) do
warn "foo"
end
end
And here are the custom assert methods.
module AssertStderr
def assert_stderr_matches(regex, message=nil)
old_stderr = $stderr
$stderr = StringIO.new
yield
assert_match(regex, $stderr.string, message)
ensure
$stderr = old_stderr
end
def assert_stderr_empty(message=nil)
# More-or-less identical.
end
end
Two methods, the bulk of which is similar. Yuck. Refactoring time.
module AssertStderr
def assert_stderr_matches(regex, message=nil, &block)
assert_match(regex, capturing_stderr(block), message)
end
def assert_stderr_empty(message=nil, &block)
assert_equal("", capturing_stderr(block))
end
def capturing_stderr(block)
old_stderr = $stderr
$stderr = StringIO.new
block.call
return $stderr.string
ensure
$stderr = old_stderr
end
end
That’s better, but it set me wondering how far I could go down this route. What about creating an assert_stderr
builder? This is what I’ve come up with so far.
def self.assert_builder(what, &check_block)
define_method("assert_stderr_#{what}") do |*args|
begin
old_stderr = $stderr
$stderr = StringIO.new
yield
check_block.call($stderr.string, *args)
ensure
$stderr = old_stderr
end
end
end
assert_builder :matches do |stderr,regex,message|
assert_match(regex, stderr, message)
end
assert_builder :empty do |stderr,message|
assert_equal("", stderr, message)
end
It looks reasonable, but it doesn’t work. It’s meant to create a method which takes a block and executes it. However, careful debugging (read: p) shows that the yield
is actually calling the check_block
parameter instead of the block that’s provided when the assert_stderr_matches
method is called. At this point, I have no idea why this is happening. I’m very confused.
It doesn’t help that you can’t really google for “ruby blocks that take blocks” and get a sensible answer.
Maybe I just need to nose around the Rails source long enough. There are examples of most forms of Ruby magic hanging around in there…