Methods & Blocks

Ruby Fundamentals — Methods & Blocks
Ruby Fundamentals
Course 1 · Chapter 4 · Methods & Blocks

🔧 Methods & Blocks

Chapter 3 used .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 greet(name) "Hello, #{name}!" end greet("Philip") # => "Hello, Philip!"

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.

⚠ Implicit return means the LAST line matters — not the "obvious" one

Because Ruby returns whatever the final expression evaluates to, a method that ends with a puts call returns nilputs 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:

def discount_price(price, is_member) return price unless is_member # early exit -- return IS needed here price * 0.9 # implicit return -- this is the last line end

🏷️ 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

def greet(name = "World") "Hello, #{name}!" end greet # => "Hello, World!" -- parentheses are optional on call too greet("Philip") # => "Hello, Philip!"

Default arguments work almost identically to PHP and Python — nothing unusual here. The more interesting difference is keyword arguments, next.

🔑 Keyword Arguments

def build_profile(name:, age: 18) "#{name}, age #{age}" end build_profile(name: "Philip", age: 33) # => "Philip, age 33" build_profile(name: "Alex") # => "Alex, age 18" -- age uses its default

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+)PythonRuby
Definitionfunction f($name, $age = 18)def f(name, age=18):def f(name:, age: 18)
Call sitef(name: "Philip")f(name="Philip")f(name: "Philip")
Can require by name?No — still positional unless namedYes, with keyword-only * markerYes — name: with no default is required

📥 Splat Args — Variable-Length Arguments

def sum_all(*numbers) # like PHP's ...$args or Python's *args numbers.sum end sum_all(1, 2, 3, 4) # => 10 -- numbers becomes [1, 2, 3, 4]

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:

def repeat(times) times.times do yield # hands control to whatever block was passed in end end repeat(3) { puts "Hi!" } # prints "Hi!" three times

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:

def describe_each(items) return items unless block_given? # graceful fallback with no block items.each do |item| yield item end end describe_each(["apple", "banana"]) do |fruit| puts "Fruit: #{fruit}" end

Passing "a Chunk of Code": PHP vs Python vs Ruby

PHPPythonRuby
MechanismClosure passed as a normal argumentLambda/function passed as a normal argumentImplicit block, not a regular argument at all
Multi-statement?Yes, closures can have a full bodyNo — lambdas are single-expression onlyYes, blocks can contain any number of statements
How the method uses itCalls 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
💎 This Is Only the Preview

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.

→ Solution

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.

→ Solution

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.

→ Solution

🎯 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.