Can I dynamically define a Ruby method that takes a block?
I know that I can dynamically define methods on a class using define_method
, and that I specify the parameters this method take开发者_高级运维s using the arity of the block.
I want to dynamically define a method that accepts both optional parameters and a block. In Ruby 1.9, this is easy because passing a block to a block is now allowed.
Unfortunately, Ruby 1.8 doesn't allow this, so the following won't work:
#Ruby 1.8
class X
define_method :foo do |bar, &baz|
puts bar
baz.call if block_given?
end
end
x = X.new
x.foo("foo") { puts "called!"} #=> LocalJumpError: no block given
Replacing the explicit block.call
with yield
doesn't fix the problem either.
This works with Ruby 1.8.7, but not 1.8.6:
class X
define_method(:foo) do |bar, &baz|
puts bar
baz.call if baz
end
end
Testing with:
X.new.foo("No block")
X.new.foo("With block") { puts " In the block!"}
p = proc {puts " In the proc!"}
X.new.foo("With proc", &p)
gives:
No block
With block
In the block!
With proc
In the proc!
(with 1.8.6 it gives syntax error, unexpected tAMPER, expecting '|'
.)
If you want optional arguments as well as block, you could try something like this:
class X
define_method(:foo) do |*args, &baz|
if args[0]
bar = args[0]
else
bar = "default"
end
puts bar
baz.call if baz
end
end
testing with:
X.new.foo
X.new.foo { puts " No arg but block"}
gives:
default
default
No arg but block
What you could do is use class_eval
with a string instead of define_method
. The downside to this (apart from not being as elegant) is that you lose lexical scoping. But this is often not needed.
精彩评论