Exception Handling
🚨 Exception Handling
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...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:
🔒 ensure — Always Runs
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
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
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
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:
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
| Concept | PHP | Python | Ruby |
|---|---|---|---|
| Try block | try { } | try: | begin |
| Catch a specific error | catch (TypeException $e) | except TypeError as e: | rescue TypeError => e |
| Always runs | finally { } | finally: | ensure |
| Throw your own | throw new Exception("msg") | raise ValueError("msg") | raise ArgumentError, "msg" |
| Retry the block itself | No keyword — manual loop | No keyword — manual loop | retry |
💻 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.
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.
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.
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.