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 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:

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.