Ruby’s awesome. It has sweet, concise syntax that makes for clean, readable code. One of these constructs is the trailing condition. In most languages where you might have to write something like:
if foo then
do_stuff
end
Ruby will let you clean that up with:
do_stuff if foo
This works just nearly all the time, but I ran into an odd problem today, where the trailing conditions were producing behavior I didn’t want.
>> foobar
NameError: undefined local variable or method `foobar' for #<Object:0x92bc998>
from (irb#1):2
>> foobar = true unless defined?(foobar)
=> nil
>> foobar
=> nil
>> unless defined?(foobar); foobar = true; end
=> true
>> foobar
=> true
Wait, what? Using the trailing conditional changes the order in which Ruby parses the statement, resulting in something like the following operations:
- Define
foobarbecause it’s referenced, set it tonil - Parse the
unlessconditional - If the condition is true, set
foobartotrue
The kicker here is that because foobar’s assignment is the first thing parsed, it’s always initialized before you ever get to the defined? statement. So instead, we run the second piece of code:
unless defined?(foobar); foobar = true; end
This runs something like the following:
- Parse the
unlesscondition. - Define
foobarbecause it’s referenced, set it tonil - If the condition is true, set
foobartotrue
Obviously this is the desired behavior. Several lessons here:
- Ruby initializes variables when they are parsed, not when the code path that contains them is run (in fact, it’ll even initialize variables that are in unreachable code paths!)
if condition then do_stuff endis not always the same asdo_stuff if condition
It’s a bit of an edge case, but it’s an edge case that had me baffled. Hopefully this post saves you some frustration.