I know that I can dynamically define methods on a class using define_method
, and that I specify the parameters this method takes 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.
Upgrading to Ruby 1.9 is unfortunately not an option for me. Is this an intractable problem, or is there a way around it?
define_method is a method defined in Module class which you can use to create methods dynamically. To use define_method , you call it with the name of the new method and a block where the parameters of the block become the parameters of the new method.
What is Metaprogramming? Metaprogramming is a technique in which code operates on code rather than on data. It can be used to write programs that write code dynamically at run time. MetaProgramming gives Ruby the ability to open and modify classes, create methods on the fly and much more.
The code def hi starts the definition of the method. It tells Ruby that we're defining a method, that its name is hi . The next line is the body of the method, the same line we saw earlier: puts "Hello World" . Finally, the last line end tells Ruby we're done defining the method.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With