Assignment in Conditionals
In Ruby, you can use the return value of an assignment in conditional expressions, e.g.,
def thing(x)
if (y = x[:y]) && y[:z]
puts "x has :y key && y has :z key"
else
puts "condition failed but y is #{y.nil? ? 'nil' : y}"
end
end
if (y = x[:y]) && y[:z]
is equivalent to setting y = x[:y]
separately, then if y && y[:z]
; this means, that y
is available anywhere (within the function scope, in this case) afterwards, including outside of the conditional expression, too.
> x1 = {}
> x2 = { y: {} }
> x3 = { y: { z: 0 } }
> xx = nil
> thing x1
condition failed but y is nil
> thing x2
condition failed but y is {}
> thing x3
x has :y key && y has :z key
> thing xx
NoMethodError: undefined method `[]' for nil:NilClass
from (pry):38:in `thing'
Note: It's good practice to enclose the assignment in parentheses to ensure that it's not a mistaken equality comparison; most linters should catch that (either in parentheses or ==
). See Safe Assignment in Condition guidelines in Rubocop's style guide for more detail.
Assignment from Conditionals
Another handy assignment feature:
result =
if x
"x"
elsif y
"y"
elsif z
"z"
end
# is equivalent to ->
if x
result = "x"
elsif y
result = "y"
elsif z
result = "z"
else
result = nil
end
You can also put the if
on the same line as result =
, but then you should probably indent everything underneath to line up (see: Indent Conditional Assignment guidelines in Rubocop's style guide for more detail).
Note also that, if no condition is met, result
will ultimately be assigned nil
(without needing to explicitly assign it in the else
case.
Conditional Assignment
Ruby has some really boss self-assignment shorthands (the Rubocop style guidelines, once again, have helpful details in the Self-Assignment section). Seriously, though, check them out.
||=
thing ||= 12
# is equivalent to any of the following:
thing = thing || 12
thing = thing ? thing : 12
thing = 12 unless thing
This is seen a lot in memoization, where a value may be retrieved or computed the first time a method is called, assigned with ||=, and then subsequent calls will return the stored value instead of re-retrieving or re-calculating the value.
class Stuff
def thing
@thing ||= "pretend this is " \
"computationally " \
"complex"
end
def other_thing
@other_thing ||= thing.present?
end
end
> stuff = Stuff.new
> stuff.thing # set @thing
"pretend this is computationally complex"
# return @thing, set @other_thing
> stuff.other_thing
true
> stuff.thing # return @thing
"pretend this is computationally complex"
Standard practice (and often enforced by linters) is to name the method the same as the instance variable.
&&=
&&= is the existence check shorthand, and is equivalent to checking the existence of a variable's value before performing some action and assigning it back to the variable. Or, as the Existence Check Shorthand section of the Rubocop style guide more coherently puts it:
This can replace ternary checks that return nil (e.g., thing = thing ? thing.upcase : nil
), conditional modification assignments (e.g., if thing ; thing = thing.upcase ; end
), etc.
> thing = "I'm here!"
> thing &&= thing.upcase
"I'M HERE!"
> thing = nil
> thing &&= thing.upcase
nil
You could also assign a value unrelated to the variable itself (e.g., thing &&= "arbitrary value"
), which would only be set if the value of thing
existed; it's hard to think of a realistic example or use-case for this, though.
Resources
- Safe Assignment in Condition @ Rubocop style guide
- Indent Conditional Assignment @ Rubocop style guide
- Self-Assignment @ Rubocop style guide
- Conditional Variable Initialization Shorthand @ Rubocop style guide
- Existence Check Shorthand @ Rubocop style guide
- Memoization article @ HoneyBadger