4

I would like to understand how define_method works and how to properly use the variables outside of the definition block. Here is my code:

class Test
  def self.plugin
    for i in 1..2
      define_method("test#{i}".to_sym) do
        p i
      end
    end
  end
  plugin
end

ob = Test.new
ob.test1 #=> 2  (I would expect 1)
ob.test2 #=> 2  (I would expect 2)

It seems that in the methods test1 and test2, the value of i is not substituted during the definition, but is computed directly on the spot when the method is called. So we see only the latest value of i, which is 2. But where does Ruby take this value from? And is there a way to let test#{i} print i?

In this particular case, I could do a workaround using __method__, but probably there is a better solution.

6
  • power of closure is expressed here.......... :) Commented May 30, 2015 at 10:40
  • Thanks for the link, the question addresses the same problem. But I am not sure the solution is provided there. At least one of the comments does not work: <pre><code>class Test for name in [ :new, :create, :destroy ] local_name = name define_method("test_#{local_name}") do puts local_name end end end ob = Test.new ob.test_new #=> destroy (I would expect 'new') ob.test_create #=> destroy (I would expect 'new') </code></pre> Commented May 30, 2015 at 11:39
  • Well.. Unmarked. But here is a similar post. Commented May 30, 2015 at 11:42
  • I've taken the code (sorry for markup) exactly from the similar post you mentioned. Just wanted to point out that the solution is proposed only in the second comment there and it does not work:-) Commented May 30, 2015 at 11:51
  • I cannot parse the part "... the value i of is not ...". Commented May 30, 2015 at 11:53

1 Answer 1

6

As mentionned in comments this is down to closures - the block passed to define_method captures the local variables from its scope (and not just their value as you found out).

for doesn't create a new scope, so your method 'sees' the change to i. If you use a block (for example with each) then a new scope is created and this won't happen. You just need to change your code to

class Test
  def self.plugin
    (1..2).each do |i|
      define_method("test#{i}".to_sym) do
        p i
      end
    end
  end
  plugin
end

which is basically what the question linked by Arup was about

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.