More Conditionals

Learning Goals

  • Meet the ternary operator!
  • Discuss postfix conditionals
  • Learn about using compound operators

Ternary Operator

In our discussion precedence, we talked about the unary operators (+ and -) to indicate whether a value was positive or negative, i.e, -15 and +4.5. All the operators we've met since then have been binary operators, i.e. the * in 10 * 5.

So if a unary operator operates (hehe) on a single value, and a binary operator operates on a pair of values, what would a ternary operator do? It'd operate on three values!

The ternary operator in Ruby doesn't do arithmatic. Instead, it performs logical operations, very similar to what we've done with if else blocks already. Ternary operations have three parts: 1) a comparison, 2) a positive results, and 3) a negative result. The syntax is comparison ? positive result : negative result. If the comparison is true, then the positive result code is executed. If the comparison is false, then the negative result code is executed. Here's an example:

# how we've done it so far
num = rand(11)
if num == 10
  puts "It's 10!"
else
  puts "It's not 10!"
end

# same code using a ternary instead
# condition ? eval_true : eval_false
num == 10 ? puts "It's 10!" : puts "It's not 10!"
# no ternary :(
def avg_score(scores)
  if scores.length > 0
    scores.sum / scores.length
  else
    0
  end  
end

# with a ternary!
def avg_score(scores)
  # condition ? eval_true : eval_false
  scores.length > 0 ? scores.inject(0, :+) / scores.length : 0
end

# and one more! No ternary:
if @account.save
  message = "Success!"
else
  message = "Problems. :("
end

# with a ternary:
message = @account.save ? "Success!" : "Problems. :("

Single-line Conditionals

We know that the if construct allows us to conditionally execute a block of code. Sometimes, those blocks are really short, like this:

if argument.nil?
  raise ArgumentError
end

By using a postfix conditional, we can trim that three-line block into a single line that reads really well:

raise ArgumentError if argument.nil?

Don't try to force the issue, but if you need to perform a conditional block that's a single line (and doesn't have a contra-case like else), then the postfix conditional is for you. This patterns works for both if and unless statements. Here are a few more examples:

# as long as my_var is truthy, do_this_method will execute
do_this_method(my_var) if my_var

# this is called a "guard clause"
return "" unless num > 0

# assign 5 to penalty.amount unless resume.is_markdown? is true
penalty.amount = 5 unless resume.is_markdown?

Compound Operators

Mathy Compound Operators

We've all written code like x = x + 4 when we want to modify a previously assigned variable. Some folks have discovered compound operators, which are shortcuts to performing this kind of operation. They only work if you've already assigned a value to the variable; trying to use them before a value is assigned will raise NoMethodError.

x = 10  # => 10
x += 50 # => 60, same as x = x + 50
x -= 10 # => 50, same as x = x - 10
x *= 2  # => 100, same as x = x * 2
x /= 20 # => 5, same as x = x / 5

puts x #=> 5 

Logical Compound Operators

There are also two logical compound operators, ||= (or-equals) and &&= (and-equals).

Similar to their mathematical cousins, these operators are shorthand for a more verbose expression. The ||= assignment only happens if the existing value of the variable is falsey. It doesn't matter whether the assignment value is truthy or not; only the existing value is checked for truthiness.

# with a truthy existing value
existing_value = "elephant"
existing_value ||= "hotdog"
existing_value.inspect #=> "elephant"

# with a falsey existing value
existing_value = nil
existing_value ||= "hotdog"
existing_value.inspect # => "hotdog"

# with falsey values everywhere
existing_value = nil
existing_value ||= false
existing_value.inspect #=> "false"
existing_value ||= nil
existing_value.inspect #=> "nil"

The &&= is pretty useless. :) Well, to be more fair, I've not found a compelling use case for it. I'm including it here for the sake of completeness. With &&=, if the existing value of the variable is truthy, then the variable is reassigned to the assignment value. Kinda like this:

# with two truthy values
existing_value = "elephant"
existing_value &&= "hotdog"
existing_value.inspect #=> "hotdog"

# with a falsey existing value and a truthy assignment value
existing_value = nil
existing_value &&= "hotdog"
existing_value.inspect #=> "nil"

# with a truthy existing value and a falsey assignment value
existing_value = "elephant"
existing_value &&= nil
existing_value.inspect #=> "nil"

Sample Code