Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How are variables bound to the body of a define_method?

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!

like image 770
Chris Avatar asked Jan 08 '10 17:01

Chris


2 Answers

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
like image 152
Kevin Avatar answered Nov 14 '22 21:11

Kevin


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.

like image 1
Myrddin Emrys Avatar answered Nov 14 '22 22:11

Myrddin Emrys