`next` related gotcha in Ruby on Rails
You are a backend developer. You have just started building, or are well versed with Ruby On Rails. You think you understand it well. Until one day, when you use next
inside a loop, inside a transaction.
Well, not sure about you, but I was like that. This was the scenario that started all this for me:
orders = Order.where(...)
orders.each do |order|
ActiveRecord::Base.transaction do
# Get updated state of order, and return early is order is already settled. Simple.
order.lock!
if order.settled?
next
end
end # End transaction
# ...
# We are here, so let's do something with order
do_something_with_open_order(order)
# ...
end # End .each loop
Looking at the code, one (at least I) would believe that if the order was settled, it would simply move to the next order in the list. But guess what: it did not. Turns out, next
does not immediately goes to the next iteration of the loop: instead, it just comes out of the transaction and runs the bottom statements. So in essence, in the above code, do_something_with_open_order
is getting run for all the orders, and the check whether the order is settled or not is just doing nothing in this particular case.
You can verify this using a simple reproduction:
numbers = [1,2,3]
numbers.each do |number|
ActiveRecord::Base.transaction do
if number.even?
puts "Inside the check. The number:#{number} is even"
next
end
end # End transaction
puts "Outside the check for number:#{number}"
end # End .each loop
Here, the output is:
Outside the check for number:1
Inside the check. The number:2 is even
Outside the check for number:2 <!-- Well, this was unexpected. -->
Outside the check for number:3
Notice, the "Outside..." statement ran even when it should have been skipped using next
in that case.
So, how do we counter this behavior to run what we need? Simple: Use functions.
def print_if_even number
ActiveRecord::Base.transaction do
# Return early is number is already settled. Simple.
if number.even?
puts "Inside the check. The number:#{number} is even"
return
end
end
puts "Outside the check for number:#{number}"
end
numbers = [1,2,3]
numbers.each do |number|
print_if_even(number)
end
This is because, unlike next
, return
works as expected (understood) by us. It simply returns from the function when it encounters the check (when the number is even), so the statements below it are not executed at all.
A lot more works differently with Ruby (like with all other programming languages in general), but we'll leave some for another day. Feel free to email me at bharatramnani94@gmail.com if you want to learn more about topics related to this, or any other topic for that matter.