While trying to brush up my Ruby skills I keep running across this case which I can't figure out an explanation for by just reading the API docs. An explanation would be greatly appreciated. Here's the example code:
for name in [ :new, :create, :destroy ]
define_method("test_#{name}") do
puts name
end
end
What I want/expect to happen is that the name
variable will be bound to the block given to define_method
and that when #test_new
is called it will output "new". Instead each defined method outputs "destroy" -- the last value assigned to the name variable. What am I misunderstanding about define_method
and its blocks? Thanks!
Blocks in Ruby are closures: the block you pass to define_method
captures the variable name
itself–not its value—so that it remains in scope whenever that block is called. That's the first piece of the puzzle.
The second piece is that the method defined by define_method
is the block itself. Basically, it converts a Proc
object (the block passed to it) into a Method
object, and binds it to the receiver.
So what you end up with is a method that has captured (is closed over) the variable name
, which by the time your loop completes is set to :destroy
.
Addition: The for ... in
construction actually creates a new local variable, which the corresponding [ ... ].each {|name| ... }
construction would not do. That is, your for ... in
loop is equivalent to the following (in Ruby 1.8 anyway):
name = nil
[ :new, :create, :destroy ].each do |name|
define_method("test_#{name}") do
puts name
end
end
name # => :destroy
for name in [ :new, :create, :destroy ]
local_name = name
define_method("test_#{local_name}") do
puts local_name
end
end
This method will behave as you expect. The reason for the confusion is that 'name' is not created once per iteration of the for loop. It is created once, and incremented. In addition, if I understand correctly, method definitions are not closures like other blocks. They retain variable visibility, but do not close over the current value of the variables.
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