It’s pretty well known that because of a deficiency in blocks arguments in Ruby 1.8 Class#define_method cannot define methods that take blocks.
def x *args, █ end # => works!
define_method(:x) {|*args,&block| } # => SyntaxError: compile error
This really sucks when you need to define methods dynamically which take blocks. Sometimes you have to define the method dynamically because it needs access to variables in the current scope. If you were to use the def syntax the scope changes and those variables would no longer be accessible. For example:
var1 = x var2 = y def my_method &block block.call(var1*var2) # => undefined local variable or method `var1' end
I came up with a simple workaround for this
var1 = x
var2 = y
define_method(:__real__my_method) do |block, *args|
block.call(var1*var2)
end
def my_method(*args, &block)
__real__my_method(block, *args)
end
We create a special “real” method which takes the block as the normal first parameter instead of as the special block parameter. Then we create a proxy method, with the name that we want to use in our code, which simply re-arranges the parameters (putting the block first) and calls the “real” method.
There’s one other possible issue. Since the method we’ll be calling from our code is still being defined with a def you might be complaining that you can’t set its name dynamically. Well there are two ways to do this. One is to just eval a string:
eval <<-EOM
def #{dynamic_name}(*args, &block)
__real__#{dynamic_name}(block, *args)
end
EOM
Sometimes though, you wan’t to use a name which has characters that the ruby parser won’t allow you to use with the def syntax. This can be accomplished with a temporary method name and a alias_method:
eval <<-EOM
def __temp_method__(*args, &block)
__send__("__real__#{dynamic_name}", block, *args)
end
EOM
alias_method dynamic_name, :__temp_method__
remove_method :__temp_method__

No longer an issue in Ruby 1.9.
Comment by Hongli Lai — October 30, 2008 @ 1:38 am
As Hongli Lai said, this is coming in Ruby 1.9. Also, if you’re going the route of using mutli-line strings, why have the second method at all? Doesn’t the following work just fine?
Comment by Drew Olson — October 30, 2008 @ 3:56 am
Hrm, gist embedding doesn’t work in comments :) Here’s the link:
http://gist.github.com/20910
Comment by Drew Olson — October 30, 2008 @ 3:57 am
Yes I’m quite aware that this has been in 1.9 since the beginning. Which is why I specified Ruby 1.8 in the title of the post :)
@Drew
All your method does is basically:
def name(*a); yield *a; end
but replacing name with whatever you specify.
The point of the post is to show you how to define methods dynamically with a block. The reason you might want to use a block is to capture the variables in the current scope which you can’t do with def or with an eval.
Or maybe I misunderstood your question, care to clarify?
Comment by coderrr — October 30, 2008 @ 4:07 am
@Chris ah, makes sense, i skimmed too quickly. the ability to define a method with a block (as a closure) that take a block makes sense.
Thanks for the clarification.
Comment by Drew — October 30, 2008 @ 4:47 am