Control Flow
๐ Control Flow
nil and false are falsy in Ruby โ everything else, including 0 and "", is truthy. This chapter puts that rule to work in if, case, and loops, and introduces the idea that will keep resurfacing for the rest of this course: in Ruby, control flow structures aren't just statements โ they're expressions that return a value.
โ if / elsif / else
age = 20 if age < 13 puts "child" elsif age < 20 puts "teenager" else puts "adult" end
No parentheses around the condition, no curly braces around the body โ Ruby uses indentation for readability (like Python) but the block is actually delimited by the end keyword (like PHP's alternate if: ... endif; syntax, though that form is rarely used in real PHP code). Note also elsif โ not else if (PHP) or elif (Python), a small but very common early typo.
The modifier form
For a single statement, Ruby lets you write the condition after the code โ a postfix form with no real equivalent in PHP or Python:
puts "You can vote" if age >= 18 puts "Still a minor" unless age >= 18
๐ unless โ Ruby's if not
unless condition runs its body when the condition is falsy โ the direct opposite of if. Neither PHP nor Python has a dedicated keyword for this; both just write if (!condition) or if not condition. Ruby style strongly prefers unless over if ! when there's no accompanying else, because it reads closer to plain English:
unless user.nil? puts "Welcome back!" end # Avoid: unless ... else ... end -- it reads backwards and is discouraged style
๐ก Ruby Is Expression-Oriented
This is the chapter's central idea: in Ruby, if and case evaluate to the value of whichever branch ran, so you can assign the result directly instead of declaring a variable first and mutating it inside each branch:
temperature = 75 description = if temperature > 80 "hot" elsif temperature > 60 "warm" else "cold" end puts description # => warm
Assigning a Conditional Result: PHP vs Python vs Ruby
| PHP | Python | Ruby | |
|---|---|---|---|
| Approach | Ternary only: $x = $a ? $b : $c; | Conditional expression: x = b if a else c | Full if/elsif/else block IS an expression |
| Multi-branch? | Needs nested ternaries (unreadable) or a temp variable | Needs nested conditional expressions or a temp variable | Any number of elsif branches, still one clean assignment |
Ruby still has a ternary operator too (age >= 18 ? "adult" : "minor"), useful for short one-liners โ but for anything with more than two branches, an expression-oriented if avoids the unreadable nested-ternary problem PHP and Python code sometimes falls into.
๐ case / when
Ruby's answer to PHP's switch (Python only gained a comparable match statement in 3.10) โ and, being Ruby, it's an expression too:
grade = "B" message = case grade when "A" then "Excellent" when "B", "C" then "Good" when "D" then "Needs improvement" else "Not a valid grade" end puts message # => Good
when "B", "C" matches either value with no fall-through logic needed โ Ruby's case never falls through to the next branch the way PHP's switch does without an explicit break. case can also match against a Range or a class, which is worth knowing exists even before classes are formally covered:
score = 85 case score when 90..100 then puts "A" when 80...90 then puts "B" # matches -- 85 is in this range else puts "Below B" end
๐ Loops โ while, until, for, and .each
i = 0 while i < 3 puts i i += 1 end i = 0 until i == 3 # the exact opposite of while -- no PHP/Python keyword equivalent puts i i += 1 end for n in 1..3 # for exists, but idiomatic Ruby rarely uses it -- see below puts n end
Unlike PHP and Python, where for loops are completely normal and common, idiomatic Ruby strongly prefers iterator methods like .each over the for keyword. It's not a hard rule, but seeing a bare for loop in Ruby code is a signal the author is likely coming from another language and hasn't adopted Ruby style yet. The next section shows why.
[1, 2, 3].each do |n| puts n end # Same thing, curly-brace block syntax -- idiomatic for short one-liners [1, 2, 3].each { |n| puts n }
Why .each over for
A Ruby for loop doesn't create its own scope โ the loop variable leaks out and stays defined after the loop ends, which most Ruby developers consider a wart carried over from C-style languages. .each is a method call that takes a block, and the block variable stays cleanly scoped to the block itself.
do...end vs { }
Both delimit a block; the convention is do...end for multi-line blocks and { } for short one-liners. This is purely a style convention, not a functional difference โ but following it is part of what makes Ruby code look idiomatic rather than translated from another language.
๐ Control Flow Keywords, Side by Side
PHP vs Python vs Ruby
| Concept | PHP | Python | Ruby |
|---|---|---|---|
| Negative-else keyword | none (if !) | none (if not) | unless |
| Switch/match | switch (needs break) | match (3.10+) | case/when (no fall-through) |
| Reverse-while loop | none | none | until |
| Idiomatic iteration | foreach | for x in list | .each do |x| ... end |
| if/case as expression | No (ternary only) | Partial (conditional expr only) | Yes, fully |
๐ป Coding Challenges
Challenge 1: Modifier if/unless
Given an integer variable stock, write two lines using the modifier form: one that prints "In stock" using if, and one that prints "Reorder soon" using unless. Test both with a value above and below 10.
Goal: Get comfortable reading and writing Ruby's postfix conditional style.
Challenge 2: case/when as an Expression
Given an integer day_number (1โ7), use case/when to assign the corresponding day name directly to a variable called day_name (no separate puts inside each branch) โ then print day_name once, after the case block.
Goal: Practice treating case as an expression rather than a sequence of side-effecting branches.
Challenge 3: for vs .each
Write the same task two ways: print the numbers 1 through 5 using a for loop, then print them again using .each on a range ((1..5).each). Add a comment noting which version is the idiomatic Ruby choice and why.
Goal: Feel the stylistic difference firsthand, not just read about it.
Once if and case return values, a whole category of PHP/Python pattern โ declare an empty variable, then reassign it inside every branch of a conditional โ mostly disappears from idiomatic Ruby. Get in the habit of asking "can I just assign this directly?" before reaching for the declare-then-mutate pattern out of PHP/Python habit.
๐ฏ What's Next
Next chapter: Methods & Blocks โ defining methods, default and keyword arguments, Ruby's implicit return, and a first proper look at blocks and yield.