Control Flow

9.1 Conditionals

if temp > 30
  puts "Hot"
elsif temp > 15
  puts "Pleasant"
else
  puts "Cold"
end

unless logged_in?        # "if not"
  redirect_to_login
end

puts "Adult" if age >= 18        # modifier form
return unless valid?             # guard clause idiom
status = paid ? "Paid" : "Due"

9.2 case / when

when uses === (case equality): classes check is_a?, ranges check inclusion, regexes check a match, and procs call themselves.

case score
when 90..100 then "A"
when 80...90 then "B"
when Integer then "Other number"
else "Invalid"
end

case input
when "start", "run" then start
when /^\d+$/ then process_number(input)
when ->(s) { s.length > 20 } then "too long"    # lambdas work via ===
end

case                                # no subject: if/elsif alternative
when x < 0 then "negative"
when x.zero? then "zero"
else "positive"
end

9.3 Pattern Matching with case / in (Ruby 3.0+)

Covered in depth in Module 23.

case { status: "ok", data: { id: 1 } }
in { status: "ok", data: { id: Integer => id } }
  puts "Found #{id}"
in { status: "error", message: }
  puts message
end

9.4 Loops

Loop control: break (with an optional value: result = loop { break 42 }), next (skip to the next iteration), redo (rare: repeat the iteration).

Idiomatic Ruby prefers iterators (each, times, map) over while/for.

i = 0
while i < 5
  i += 1
end

until i.zero?
  i -= 1
end

loop do
  input = gets&.chomp
  break if input.nil? || input == "quit"
  next if input.empty?
  process(input)
end

for n in 1..3        # works but is rarely used (the loop variable leaks into the outer scope)
  puts n
end

begin                # do-while equivalent
  attempt += 1
end while attempt < 3

5.times { |i| puts i }
[1, 2, 3].each { |n| puts n }
1.step(10, 2) { |n| puts n }

9.5 catch / throw (Non-Local Exit)

result = catch(:found) do
  matrix.each do |row|
    row.each { |cell| throw :found, cell if cell > 100 }
  end
  nil
end