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
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:
🙅 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:
💡 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:
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:
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:
🔂 Loops — while, until, for, and .each
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.
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.