Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialJiaming Zhang
10,170 PointsA weird bug when using Ruby define_methd inside for loop
I try to use Ruby define_method to dynamically define a class like this:
class Foo
def bar1
return 1
end
def bar2
return 2
end
def bar3
return 3
end
end
Here is my implementation:
class Foo
for i in 1..3
define_method("bar#{i}") do
return i
end
end
end
When I run the following three commands, all of them return 3
Foo.new.bar1 # return 3
Foo.new.bar2 # return 3
Foo.new.bar3 # return 3
Anyone know how to fix this?
I kinds of answer my question below.....
3 Answers
Jiaming Zhang
10,170 PointsHey Nathan Williams and Gaylen Miller,
Thanks for your replies. Your answers reminds me that this weird bug may be caused by the way how Ruby FOR loop handles closure. I found my answer in this StackOverflow post: http://stackoverflow.com/questions/10396475/ruby-for-loop-a-trap
Ruby for loop create one scope during the whole execution. It can be best explained using the following code:
results = []
for i in 1..3
results << lambda { i }
p results.map(&:call)
# during the 1st loop, the output is [1]
# during the 2nd loop, the output is [2, 2]
# during the 3rd loop, the output is [3, 3, 3]
end
p results.map(&:call) # => [3,3,3]
By the time when the FOR loop finishes execution, the variable i inside the scope of the for loop is set to the value 3. All of variables i in the anonymous function points this i.
Nathan Williams
Python Web Development Techdegree Student 6,851 PointsHmmmm... I'm not sure exactly what's going on there either (though now I'm really curious), but this should do what you're looking for:
[nathwill@wyrd ~]$ irb
irb(main):013:0> 3.times do |i|
irb(main):014:1* define_method("bar#{i}") do
irb(main):015:2* return i
irb(main):016:2> end
irb(main):017:1> end
=> 3
irb(main):018:0> bar1
=> 1
irb(main):019:0> bar2
=> 2
irb(main):020:0> bar3
=> 3
Hope that helps!
Jiaming Zhang
10,170 PointsIt seems that there is somethings wrong with for loop.
When I change the code to this
class Bar
1.upto(3) do |i|
self.send(:define_method,"foo_#{i}") do
return i
end
end
end
or this:
class Bar
[1,2,3].each do |i|
self.send(:define_method,"foo_#{i}") do
return i
end
end
end
and even this:
class Bar
(1..3).each do |i|
self.send(:define_method,"foo_#{i}") do
return i
end
end
end
It works!
Gaylen Miller
19,286 PointsHere's at least part of the explanation. With the For loop, there are three methods being defined to view the same object.
irb(main):001:0> class Foo
irb(main):002:1> for i in 1..3 do
irb(main):003:2* define_method("bar#{i}") {"#{i} #{i.object_id}"}
irb(main):004:2> end
irb(main):005:1> end
=> 1..3
irb(main):006:0> Foo.new.bar1
=> "3 7"
irb(main):007:0> Foo.new.bar2
=> "3 7"
irb(main):008:0> Foo.new.bar3
=> "3 7"
irb(main):009:0>
With the "times" way, there are three different objects being displayed.
irb(main):011:0> 3.times do |i|
irb(main):012:1* define_method("bar#{i}") {"#{i} #{i.object_id}"}
irb(main):013:1> end
=> 3
irb(main):014:0> bar0
=> "0 1"
irb(main):015:0> bar1
=> "1 3"
irb(main):016:0> bar2
=> "2 5"