TL;DR Blocks reset their scope on each iteration. You can do return_response = nil
prior to entering the loop, and it should fix your problem because it won't get reset every iteration.
This is a really interesting question because the output is somewhat misleading. I did some more research because I didn't like my answer, and this is what I came up with. (FYI, I ran all this on Ruby 2.1.1)
Variable Presence
In Ruby, variable presence in a single scope is determined by the parser. There is a really good explanation here, but for a simple example:
if false
fnord = 42
end
fnord # => nil
Contrary to this (you'll have to reload irb or otherwise reset the scope)
fnord # => NameError: undefined local variable or method `fnord' for main:Object
if false
fnord = 42
end
It's interesting behavior, and I highly recommend reading the above linked stack overflow answer for more details.
Block Scoping
Blocks in Ruby reset their local scopes every iteration.
5.times do
# This "if false" is here because otherwise the scope reset would cause x
# to be a NameError, as above. If you delete the following 3 lines it
# will raise that error when running "p x".
if false
x = 10
end
p x
x = 20
end
The x = 20
is never retained - you'll get nil printed, five times in a row, because x has been parsed (so you won't get the NameError above) but it hasn't been assigned. You could define x outside of the block scope to keep the value (or even just let the parser parse x outside the block scope, without actually doing an assignment!)
x = nil
5.times do
p x
x = 20
end
That will result in nil, then 20, 20, 20, 20. The scope of x
in this case is outside the block scope (and blocks can access local variables in the scope around them). Even if you'd used the if false
mechanism to "declare" x without assigning it, it would still work.
So, for your situation, the issue isn't that return_response is being scoped to the if
section of the if/else
statement, it's that it isn't assigned at all the second time around (but it does get parsed, so it has a value of nil, rather than raising a NameError). Let's simplify your situation a bit:
(0..1).each do |i|
if i == 0
x = 10
p x
else
p x
end
end
This prints 10, and then nil, which mirrors the problem you were having. You couldn't fix it by just "declaring" the variable prior to the if statement, but still inside the block scope (or by using block-local variables, which would do the same thing):
(0..1).each do |i|
x ||= nil # note: this is not something you'd probably do in Ruby
if i == 0
x = 10
p x
else
p x
end
end
This still prints 10, then nil. You have to move the variable outside the block scope for it to not get reset each time.
x = nil # outside the block scope
(0..1).each do |i|
if i == 0
x = 10
p x
else
p x
end
end
This will print 10, 10.
It looks like from your code you're wanting to build up the return_response from each row as you iterate. You can do return_response = nil
prior to entering the loop, and it should fix your problem because it won't get reset every iteration. A more Ruby-like approach would be to assign return_response = []
prior to entering the loop and appending each record (you wouldn't need to check for i == 0 in this case). And if you didn't need the intermediate logging, you could probably get away with this
return_response = response_array.map { |response_line| response_line[:value]['value'] }.join("X-LINE-BREAK.")
I.e. map each item in the response array to its [:value]['value']
, then join all those into string with "X-LINE-BREAK."
in between each.
PS: These two answers also provide some information on other peculiarities with block scoping, variables, and loops, though they don't apply to your particular case: https://stackoverflow.com/a/1654665/4280232 and https://stackoverflow.com/a/21296598/4280232.
The previous answer I wrote here was incorrect, so I replaced it. If that was the wrong approach, please let me know.