Methods & Blocks
🔧 Methods & Blocks
.each do |n| ... end without fully explaining what was happening. This chapter defines methods properly — arguments, implicit return, naming conventions — and then opens up what a block actually is: not a regular argument, but a distinct, implicit part of a Ruby method call unlike anything PHP or Python's callables offer directly.
📐 Defining a Method
def...end defines a method, with no braces and no function/def-with-colon syntax to worry about beyond that. Notice there's no return keyword — the method's last evaluated expression is automatically its return value. This is Ruby's implicit return, and it's used constantly.
Because Ruby returns whatever the final expression evaluates to, a method that ends with a puts call returns nil — puts always returns nil, regardless of what it printed. This trips up almost everyone coming from PHP or Python, where forgetting an explicit return in a similar spot would give a much more obvious "why is this None/null?" bug. In Ruby, it fails just as silently, but looks like working code at a glance.
The explicit return keyword still exists, and is the right choice for an early exit from inside a conditional:
🏷️ Naming Conventions: ? and !
Two method-naming conventions carry real meaning in Ruby and aren't just style preferences:
? — returns true/false
A method ending in ? signals it returns a boolean, by convention (not enforced by the language). You've already seen this: 5.even?, nil.nil?, array.empty?. PHP and Python have no equivalent naming convention — you'd typically prefix with is_ instead.
! — "dangerous" / mutates in place
A method ending in ! signals it mutates the receiver directly or is otherwise more "dangerous" than its non-! counterpart — "ruby".upcase returns a new string, while "ruby".upcase! modifies the original string in place. Always check what the non-! version does first; it's usually the safer default.
⚙️ Default Arguments
Default arguments work almost identically to PHP and Python — nothing unusual here. The more interesting difference is keyword arguments, next.
🔑 Keyword Arguments
name: with no default is a required keyword argument — calling build_profile without it raises an ArgumentError. This mirrors the symbol-key hash shorthand from Chapter 2 for a reason: keyword arguments and symbol-keyed hashes share the same colon syntax deliberately.
Named Arguments: PHP vs Python vs Ruby
| PHP (8+) | Python | Ruby | |
|---|---|---|---|
| Definition | function f($name, $age = 18) | def f(name, age=18): | def f(name:, age: 18) |
| Call site | f(name: "Philip") | f(name="Philip") | f(name: "Philip") |
| Can require by name? | No — still positional unless named | Yes, with keyword-only * marker | Yes — name: with no default is required |
📥 Splat Args — Variable-Length Arguments
Ruby's single-splat *args and double-splat **kwargs (for collecting arbitrary keyword arguments into a hash) work almost exactly like Python's identical syntax — one of the closer matches between the two languages in this chapter.
🧊 Blocks — Not a Regular Argument
Here's the real departure from PHP and Python. In both of those languages, if you want to pass "a chunk of code" into a function, you pass it as a regular argument — a closure, an anonymous function, a lambda. In Ruby, every method call can carry an implicit block that isn't a normal argument at all:
yield calls whatever block was attached to the current method call — the block itself doesn't appear anywhere in repeat's parameter list. You can check whether a block was even given with block_given?, and pass values into the block by giving yield arguments:
Passing "a Chunk of Code": PHP vs Python vs Ruby
| PHP | Python | Ruby | |
|---|---|---|---|
| Mechanism | Closure passed as a normal argument | Lambda/function passed as a normal argument | Implicit block, not a regular argument at all |
| Multi-statement? | Yes, closures can have a full body | No — lambdas are single-expression only | Yes, blocks can contain any number of statements |
| How the method uses it | Calls the closure variable directly, e.g. $callback() | Calls the function variable directly, e.g. callback() | yield — the block isn't a named variable at all |
Blocks can be converted into real objects (Procs and Lambdas) that can be stored in a variable and passed around explicitly — using the & operator you'll have seen hinted at once or twice already. That's genuinely the single most distinctive feature of Ruby as a language, and it gets a full chapter of its own (Chapter 8) once classes and modules are in place. For now, the goal is just recognizing do...end/{ } as a block, and yield as how a method hands control to one.
💻 Coding Challenges
Challenge 1: Implicit Return with Keyword Arguments
Write a method full_name that takes required keyword arguments first: and last:, and an optional keyword argument middle: defaulting to nil. Return the combined name (handling the case where middle is nil) using implicit return — no return keyword anywhere in the method.
Goal: Practice keyword arguments and trusting implicit return for the method's final value.
Challenge 2: Splat Args
Write a method average that accepts any number of numeric arguments using *numbers and returns their average. Handle the edge case of zero arguments by returning 0 instead of dividing by zero.
Goal: Get comfortable with *args collecting a variable number of arguments into an array.
Challenge 3: A Method That Yields
Write a method with_timing that takes no arguments, records nothing fancy (no real timer needed) but prints "Starting...", then yields, then prints "Done!". Use block_given? to print "No block given!" instead if no block was passed. Call it once with a block and once without.
Goal: Write your own yield-based method instead of just using one Ruby provides.
🎯 What's Next
Next chapter: Arrays & Hashes Deep Dive — Enumerable methods like map, select, and reduce, which lean heavily on the block mechanics this chapter just introduced.