Exception Handling

Ruby Intermediate/Advanced — 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 Basicsmethod_missing, define_method, send, and respond_to?, the tools behind Ruby's reputation for letting code write code.