Exception Handling

Ruby Intermediate/Advanced
Course 2 ยท Chapter 1 ยท Exception Handling

๐Ÿšจ Exception Handling

Ruby Fundamentals covered the language's building blocks โ€” now Course 2 turns to writing more robust, production-shaped code. First up: what happens when something goes wrong. Ruby's begin/rescue/ensure will feel structurally familiar from PHP's try/catch/finally or Python's try/except/finally, with a couple of Ruby-specific twists worth slowing down for.

๐Ÿ›Ÿ begin / rescue / end

begin
  10 / 0
rescue ZeroDivisionError => e
  puts "Can't divide by zero: #{e.message}"
end

begin...end wraps the risky code, and rescue SpecificError => e catches a matching exception, binding it to the local variable e. Multiple rescue clauses are allowed, checked top to bottom, exactly like PHP or Python's multiple catch/except blocks:

begin
  risky_operation
rescue ZeroDivisionError => e
  puts "Math problem: #{e.message}"
rescue ArgumentError => e
  puts "Bad argument: #{e.message}"
rescue => e   # bare rescue -- catches StandardError and its subclasses, see below
  puts "Something else went wrong: #{e.message}"
end

๐Ÿ”’ ensure โ€” Always Runs

begin
  puts "Trying..."
  raise "Something broke"
rescue => e
  puts "Rescued: #{e.message}"
ensure
  puts "Cleaning up, no matter what happened"   # always runs
end

ensure runs whether an exception was raised or not, and even if the rescue block itself re-raises โ€” the direct equivalent of PHP and Python's finally, just with a different keyword. Common use: closing a file handle or a database connection regardless of what happened above it.

โš ๏ธ raise โ€” Throwing Your Own Exceptions

raise "Something went wrong"              # raises a plain RuntimeError with this message
raise ArgumentError, "Age can't be negative"   # raises a specific exception class

A bare raise "message" is a convenient shorthand โ€” Ruby wraps it in a RuntimeError automatically. For anything beyond a quick script, prefer the two-argument form (or a custom class, below) so callers can rescue a specific, meaningful error type instead of every possible RuntimeError in the codebase.

๐Ÿ” retry โ€” Ruby-Specific, No Direct PHP/Python Equivalent

attempts = 0

begin
  attempts += 1
  puts "Attempt #{attempts}"
  raise "Simulated failure" if attempts < 3
  puts "Succeeded!"
rescue => e
  retry if attempts < 3   # jumps back to the start of begin, NOT to rescue
  puts "Giving up: #{e.message}"
end
โš  retry can loop forever if you don't cap it

retry jumps straight back to the top of the begin block, running everything inside it again โ€” including the line that raised the exception in the first place. Without a counter (like attempts above) or some other cutoff condition, a persistently-failing operation combined with an unconditional retry will loop indefinitely. Neither PHP nor Python has a keyword for this pattern; you'd write a manual loop with your own retry-counting logic instead.

๐Ÿ—๏ธ Custom Exception Classes

class InsufficientFundsError < StandardError
  def initialize(msg = "Not enough funds for this transaction")
    super
  end
end

def withdraw(balance, amount)
  raise InsufficientFundsError if amount > balance
  balance - amount
end

begin
  withdraw(50, 100)
rescue InsufficientFundsError => e
  puts e.message   # => Not enough funds for this transaction
end

Custom exceptions inherit from StandardError (not the bare Exception class โ€” see the tip box below), pass a default message to super, and can then be rescued specifically by name โ€” exactly like extending PHP's Exception class or Python's Exception subclassing.

โžฐ The Modifier rescue

Echoing Chapter 3's modifier if/unless, there's a compact inline form for simple cases:

value = Integer("not a number") rescue 0
puts value   # => 0  -- falls back instead of crashing

Useful for a one-line fallback, but avoid it for anything where you'd actually want to know which exception occurred โ€” the modifier form rescues everything indiscriminately, with no way to distinguish error types.

๐Ÿ“œ Error Handling, Side by Side

PHP vs Python vs Ruby

ConceptPHPPythonRuby
Try blocktry { }try:begin
Catch a specific errorcatch (TypeException $e)except TypeError as e:rescue TypeError => e
Always runsfinally { }finally:ensure
Throw your ownthrow new Exception("msg")raise ValueError("msg")raise ArgumentError, "msg"
Retry the block itselfNo keyword โ€” manual loopNo keyword โ€” manual loopretry

๐Ÿ’ป Coding Challenges

Challenge 1: begin/rescue/ensure

Write a method safe_divide(a, b) that divides a by b inside a begin/rescue block, rescuing ZeroDivisionError specifically and returning nil in that case. Add an ensure clause that prints "Division attempted" every time, regardless of outcome.

Goal: Get the basic begin/rescue/ensure shape under your fingers with a concrete, testable example.

โ†’ Solution

Challenge 2: A Custom Exception

Define a custom exception class InvalidAgeError < StandardError with a default message. Write a method set_age(age) that raises it when age is negative or over 130. Call it inside a begin/rescue that catches InvalidAgeError specifically and prints its message.

Goal: Practice defining and raising your own meaningful exception type instead of a generic one.

โ†’ Solution

Challenge 3: retry With a Cap

Simulate a flaky network call: a method that raises an exception the first two times it's called and succeeds on the third. Use begin/rescue/retry to automatically retry up to 3 times, printing which attempt is running each time, and giving up gracefully if it never succeeds.

Goal: Practice retry safely, with a hard cap that prevents an infinite loop.

โ†’ Solution

๐Ÿ’Ž Rescue StandardError, Not Exception

A bare rescue (with no class named) actually rescues StandardError and its subclasses โ€” not every possible exception. This is deliberate: Ruby's Exception hierarchy also includes things like SystemExit and NoMemoryError, which you almost never want to silently swallow. Always inherit custom exceptions from StandardError (as this chapter's examples do), and be very deliberate any time you're tempted to write rescue Exception instead of a plain rescue โ€” it's a common source of bugs where a program keeps limping along after it really should have been allowed to crash.

๐ŸŽฏ What's Next

Next chapter: Metaprogramming Basics โ€” method_missing, define_method, send, and respond_to?, the tools behind Ruby's reputation for letting code write code.