Skip to Main Content

Shorthand Ruby Operators for Assignment Conditionals

Ruby programming concepts illustrated with a golden logic gate diagram.

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:

Use &&= to preprocess variables that may or may not exist. Using &&= will change the value only if it exists, removing the need to check its existence with if.

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

Related Posts