Control Structures in Ruby

Conditional statements are used to alter the logic of the execution of the flow based on certain conditions. Here are a few conditional statements available to developers as a part of ruby.

Ruby if statement

The block of code mentioned in the if statement is executed only when the condition mentioned in the if statement becomes true.

age = gets.chomp.to_i   
if age >= 18   
	puts "You are eligible to vote."   
end

Flip-Flop operator

The flip-flop operator is used between two conditions in a conditional statement

(1..5).select do |e|
  e if (e == 2) .. (e == 4)
end
# => [2, 3, 4]

The condition evaluates to false until the first part becomes true. Then it evaluates to true until the second part becomes true. After that it switches to false again.

This example illustrates what is being selected:

[1, 2, 2, 3, 4, 4, 5].select do |e|
  e if (e == 2) .. (e == 4)
end
# => [2, 2, 3, 4]

The flip-flop operator only works inside ifs (including unless) and ternary operator. Otherwise it is being considered as the range operator.

(1..5).select do |e|
  (e == 2) .. (e == 4)
end
# => ArgumentError: bad value for range

It can switch from false to true and backwards multiple times:

((1..5).to_a * 2).select do |e|
  e if (e == 2) .. (e == 4)
end
# => [2, 3, 4, 2, 3, 4] 

while

A while loop keeps executing the code block till the conditional statement is rendered false:

i = 0
while i < 5
  puts "Iteration ##{i}"
  i +=1
end

Until statement

An until loop keeps executing the code block till the conditional statement is rendered true:

i = 0
until i == 5
  puts "Iteration ##{i}"
  i +=1
end

Loop control with break, next, and redo

The flow of execution of a Ruby block may be controlled with the break, next, and redo statements.

A break statement terminates an ongoing iteration and stops the further iterations of the loop. It is used in conditions when we want the compiler to completely come out of the loop:

actions = %w(run jump swim exit macarena)
index = 0

while index < actions.length
  action = actions[index]

  break if action == "exit"

  index += 1
  puts "Currently doing this action: #{action}"
end

# Currently doing this action: run
# Currently doing this action: jump
# Currently doing this action: swim

The next statement terminates the ongoing iteration and starts with the next iteration immediately:

actions = %w(run jump swim rest macarena)
index = 0

while index < actions.length
  action = actions[index]
  index += 1

  next if action == "rest"

  puts "Currently doing this action: #{action}"
end

# Currently doing this action: run
# Currently doing this action: jump
# Currently doing this action: swim
# Currently doing this action: macarena

The redo statement terminates the ongoing statement and reiterates the terminated iteration:

actions = %w(run jump swim sleep macarena)
index = 0
repeat_count = 0

while index < actions.length
  action = actions[index]
  puts "Currently doing this action: #{action}"

  if action == "sleep"
    repeat_count += 1
    redo if repeat_count < 3
  end

  index += 1
end

# Currently doing this action: run
# Currently doing this action: jump
# Currently doing this action: swim
# Currently doing this action: sleep
# Currently doing this action: sleep
# Currently doing this action: sleep
# Currently doing this action: macarena

Block result values

In both the break and next statements, a value may be provided, and will be used as a block result value:

even_value = for value in [1, 2, 3]
  break value if value.even?
end

puts "The first even value is: #{even_value}"

# The first even value is: 2

return vs. next: non-local return in a block

Consider this broken snippet:

def foo
  bar = [1, 2, 3, 4].map do |x|
    return 0 if x.even?
    x
  end
  puts 'baz'
  bar
end
foo # => 0

One might expect return to yield a value for map's array of block results. So the return value of foo would be [1, 0, 3, 0]. Instead, return returns a value from the method foo. Notice that baz isn't printed, which means execution never reached that line.

next with a value does the trick. It acts as a block-level return.

def foo
  bar = [1, 2, 3, 4].map do |x|
    next 0 if x.even?
    x
  end
  puts 'baz'
  bar
end
foo # baz
    # => [1, 0, 3, 0]

In the absence of a return, the value returned by the block is the value of its last expression.

Ruby if else

The code in if block is executed if the conditional statement is true. The else block is executed if the condition is false.

age = gets.chomp.to_i   
if age >= 18   
	puts "You are eligible to vote."   
else   
	puts "You are not eligible to vote."   
end  

Ruby if else if (elsif)

Ruby if else if statement tests the condition. The if block statement is executed if condition is true otherwise else block statement is executed.

age = gets.chomp.to_i
if age > 0 and age <= 2
  puts "baby"
elsif age >= 3  and age <= 12
  puts "child"
elsif age >=13 and age <= 19
  puts "teenager"
elsif age >= 20 and age <= 60
  puts "adult"
else 
  puts "old"
end

Try

Ruby ternary Statement

In Ruby ternary statement, the if statement is shortened. First it evaluates an expression for true or false value then execute one of the statements.

age = gets.chomp.to_i
status = (age < 18 ? true : false)

Ruby Case Statement

Ruby uses the case keyword for switch statements.

As per the Ruby Docs:

  • Case statements consist of an optional condition, which is in the position of an argument to case, and zero or more when clauses. The first when clause to match the condition (or to evaluate to Boolean truth, if the condition is null) “wins”, and its code stanza is executed. The value of the case statement is the value of the successful when clause, or nil if there is no such clause.
  • A case statement can end with an else clause. Each when a statement can have multiple candidate values, separated by commas.

Example:

case x
when 1,2,3
  puts "1, 2, or 3"
when 10
  puts "10"
else
  puts "Some other number"
end

Shorter version:

case x
when 1,2,3 then puts "1, 2, or 3"
when 10 then puts "10"
else puts "Some other number"
end

The value of the case clause is matched with each when clause using the === method (not ==). Therefore it can be used with a variety of different types of objects.

A case statement can be used with Ranges:

case 17
when 13..19
  puts "teenager"
end

A case statement can be used with a Regexp:

case "google"
when /oo/
  puts "word contains oo"
end

A case statement can be used with a Proc or lambda:

case 44
when -> (n) { n.even? or n < 0 }
  puts "even or less than zero"
end

A case statement can be used with Classes:

case x
when Integer
  puts "It's an integer"
when String
  puts "It's a string"
end

By implementing the === method you can create your own match classes:

class Empty
  def self.===(object)
    !object or "" == object
  end
end

case ""
when Empty
  puts "name was empty"
else
  puts "name is not empty"
end

A case statement can be used without a value to match against:

case
when ENV['A'] == 'Y'
  puts 'A'
when ENV['B'] == 'Y'
  puts 'B'
else
  puts 'Neither A nor B'
end

A case statement has a value, so you can use it as a method argument or in an assignment:

description = case 16
              when 13..19 then "teenager"
              else ""
              end

Truthy and Falsy values

In Ruby, there are exactly two values which are considered "falsy", and will return false when tested as a condition for an if expression. They are:

  • nil
  • boolean false

All other values are considered "truthy", including:

  • numeric zero (Integer or otherwise)
  • "" - Empty strings
  • "\n" - Strings containing only whitespace
  • [] - Empty arrays
  • {} - Empty hashes

Take, for example, the following code:

def check_truthy(var_name, var)
  is_truthy = var ? "truthy" : "falsy"
  puts "#{var_name} is #{is_truthy}"
end

check_truthy("false", false)
check_truthy("nil", nil)
check_truthy("0", 0)
check_truthy("empty string", "")
check_truthy("\\n", "\n")
check_truthy("empty array", [])
check_truthy("empty hash", {})

Will output:

false is falsy
nil is falsy
0 is truthy
empty string is truthy
\n is truthy
empty array is truthy
empty hash is truthy

Inline if/unless

A common pattern is to use an inline, or trailing, if or unless:

puts "x is less than 5" if x < 5

This is known as a conditional modifier, and is a handy way of adding simple guard code and early returns:

hungry = true
unless hungry
	puts "I'm learning Ruby course!"
else
	puts "Time to eat!"
end

It is not possible to add an else clause to these modifiers. Also it is generally not recommended to use conditional modifiers inside the main logic -- For complex code one should use normal if, elsif, else instead.