Conditionals and Flow Control

How to bake logic into the flow of your scripts.

Scroll down...

Content

Resources

Comments

Think about how the Ruby interpreter moves through your code. Sometimes you want it to execute a certain chunk of code, other times you don't. In this lesson, you'll see the different ways of controlling the flow of your program.

"Truthiness"

To create conditions to control your program's flow, you first need to understand which types of things Ruby considers "true" and which ones it considers "false". "Truthiness" and "Falsiness" are ways of saying "what evaluates to true in a conditional statement?" and "what evaluates to false in a conditional statement"? In many languages, there is some nuance to that question.

In Ruby, it's simple: nil and false are false and that's it. Everything else is "truthy". It's a very truthy language.

That doesn't mean that these items are actually equivalent to the boolean value true (they of course have values of their own)... but if you put them in a statement looking for truth, you'll get it. Weird? Keep reading.

# They don't actually equal true...
> 1 == true
#=> false
> 1 == false
#=> false  # make up your mind!
> "hello" == true
#=> false

# ...but they evaluate to true
> puts "But now I'm true!" if(1)
But now I'm true!
#=> nil
> puts "you'll never see this" if(nil)
#=> nil

If

An if statement is pretty straightforward too. Supply a condition and, if it's true (or truthy!), the code will be executed. If you supply an else clause, then that code is executed otherwise. Everything proceeds normally from that point forward. Remember that the parentheses are implicit.

if some_variable.is_a?(String)
    # do some code if some_variable is a string
else
    # this code will not run unless the variable is NOT a String
end

Unless

unless is the opposite of if (which should make sense from the English of it). So it will jump into the included code... UNLESS the statement is true.

unless can also have an else clause, though it's less common because then you need to scratch your head to think about it so your code gets a bit less readable.

unless home_team.won_the_super_bowl?
    puts "I need to drown my sorrows in ice cream"
end

Single Line Ifs

Want some more Ruby awesomeness? Put your if or unless statements in a single line:

dispatch_vampire_hunters if current_user.is_a?(Vampire) 
send_vampires_home unless still_dark_outside?

Comparison Operators

Use Comparison Operators as the building blocks to construct your conditional statements. There are some simple ones that you should already be familiar with: ==, <, >, >=, and <=. != is "not equal".

> true == false
#=> false
> true != false
#=> true

The Spaceship Operator

The Spaceship Operator <=> is a special one that comes up because it actually gives three different possible outputs depending on whether the left side is greater than, less than, or equal to the right side.

> 1 <=> 1000
#=> -1
> 1 <=> 1
#=> 0
> 1 <=> -1000
#=> 1

The Spaceship can be useful because, like basically everything else, it's actually a method and you can override it in your own classes.

Advanced Spaceships

The Spaceship Operator is most commonly used in sorting methods. Imagine that you created a Person class and you wanted to sort an array of Person objects. You first have to teach Ruby how to compare two Persons by defining the <=> method for the Person class:

class Person
  attr_accessor :last_name

  def initialize(last_name)
    @last_name = last_name
  end

  # To compare two people, use their last names
  def <=> (other_person)  
    self.last_name <=> other_person.last_name
  end
end 

# IRB
> person1 = Person.new("Smith")
#=> #<Person:0x007fd4e30073b8 @last_name="Smith"> 
> person2 = Person.new("Jones")
#=> #<Person:0x007fd4e28e98d8 @last_name="Jones"> 
> people = [person1, person2]
#=> [#<Person:0x007fd4e30073b8 @last_name="Smith">, #<Person:0x007fd4e28e98d8 @last_name="Jones">] 
> people.sort  # the order gets reversed!
#=> [#<Person:0x007fd4e28e98d8 @last_name="Jones">, #<Person:0x007fd4e30073b8 @last_name="Smith">] 

Logical Operators

Logical Operators go one step beyond simple comparisons and allow you to start chaining together several comparisons into a single statement. That lets you build more interesting and complex if statements. The most common are:

  • && aka and, meaning both sides must be true for the full expression to evaluate to true
  • || (the pipe symbol, usually on the same key as the backslash) aka or, meaning that if EITHER of the two sides is true, the expression is true (else false)
  • ! aka not(), which reverses the expression from true to false or false to true

"In the wild" you'll probably see some long, complex or just plain odd looking if statements. The trick is to start breaking everything that looks like a conditional piece into what it evaluates to... either true or false.

Order of Operations

So what do you evaluate first? Ruby logical expressions use a similar order of operations to normal math: left to right unless there are parentheses.

> ( false || true ) && !(true && true ) 
#=> false

Lazy Evaluation from Lazy Ruby

Ruby will only evaluate far enough in a logical expression to determine that the expression is definitively true or false.

> puts("this isn't important") && puts("THIS IS IMPORTANT!!!")
"this isn't important"
#=> nil  
# we never hit the second puts because
# puts returns nil, which is falsy
# and so Ruby evaluates only the first
# part because it doesn't need the second
# to know that the expression will be false

The Return Value is... Returned

Ruby will return whatever is returned by the last part of the logical expression to get evaluated. You might expect it to return a simple true or false but NO! Ruby is comfortable giving you an actual object instead of true because that object is guaranteed to be "truthy".

# remember that strings are truthy!
> "I got evaluated!!!" || nil || "I did not :("
#=> "I got evaluated!!!"
> ("I got evaluated!!!" || nil) && "And so did I!"
#=> "And so did I!"
> 7 || nil
#=> 7  # since 7 was the last evaluated expression

That's important because we can actually use methods as part of our logical chains -- the method is run as normal and its return value gets tested for truthiness like any other truthy or falsy value. Sometimes we want to use this lazy evaluation to only run one method depending on the return value of the other:

def falsy_method
  puts "falsy because puts returns nil"
end
def truthy_method
  puts "getting truthy!"
  "truthy because a string, like all else, is truthy"
end

# IRB
> falsy_method && truthy_method
falsy because puts returns nil
#=> nil
> false || truthy_method || falsy_method
getting truthy!
#=> "truthy because a string, like all else, is truthy"
# note that the falsy method never got run

If this seems a bit much to swallow right off the bat, keep it in the back of your mind until you first see it in action then it will click.

Additional Fun Stuff

Here are some more advanced conditional logic items you're likely to encounter as well.

Ternary Operators

You may have seen some oddly compact and strange looking statements that appeared to be if statements under the hood. That's probably because they use the Ternary Operator, which is a shorthand notation for a simple if that separates the different parts using the ? and : like:

condition ? do_this_if_true : do_this_if_false

So:

> true ? puts "I like truth" : puts "not gonna happen"
"I like truth"
#=> nil

Case Statements

You can also nest if statements inside one another. But if you do, sometimes it gets a little crazy and you find yourself 6 levels deep. You probably need to rethink your strategy, but you might be a candidate for a case statement.

case is used for those situations where you're really just checking to see if something equals any one of a number of clear but different options. It basically lets you construct a chain of logic that says

"if x equals option_a, do this, if it equals option_b, do this, if it equals option_c, do this... and otherwise do this."

For example:

case current_user.energy   # Assume it's an integer 1-3
when 3
    puts "Go run a marathon!"
when 2
    puts "Go for a walk."
when 1
    puts "Go take a nap"
else
    puts "You're only supposed to have energy of 1,2 or 3..."
end

"Or Equals" -- ||=

||= is a sneaky expression that takes advantage of Ruby's natural laziness -- it basically expands to thing_a || thing_a = thing_b. So if thing_a hasn't been set to anything, it becomes thing_b, otherwise it keeps its original value.

Why? The reasoning is a bit complex and you don't need to know exactly why it works, but we'll go over it for completeness in case you've got a nerd itch that needs scratching:

If thing_a hasn't yet been assigned to anything, it is nil and Ruby then checks the right side of the || to see if that might be true, which involves running the expression to set thing_a = thing_b. If it has already been assigned a value, it just keeps that value like normal. This is another sneaky trick used by programmers in situations like when you don't want to override whatever's already been set, but you want something to be there (like which url originally referred the user to your site).



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

There are no additional resources for this lesson just yet!

Sorry, comments aren't active just yet!