Exceptions in Depth
Prerequisites: Basic error handling, Inheritance
The beginner lesson covered rescue. Real programs also need to
raise errors, clean up after them, and define their own error
types.
raise
def withdraw(balance, amount)
raise ArgumentError, "amount must be positive" unless amount.positive?
balance - amount
end
raise ErrorClass, "message" stops the method and sends the error up
to whichever caller rescues it.
Custom error classes
Define your own errors by inheriting from StandardError. Callers
can then rescue exactly the problems they know how to handle:
class InsufficientFunds < StandardError
attr_reader :shortfall
def initialize(shortfall)
@shortfall = shortfall
super("insufficient funds: short by #{shortfall}")
end
end
def withdraw(balance, amount)
raise InsufficientFunds.new(amount - balance) if amount > balance
balance - amount
end
begin
withdraw(50, 80)
rescue InsufficientFunds => e
puts e.message
puts "Please deposit #{e.shortfall} more"
end
insufficient funds: short by 30
Please deposit 30 more
Inherit from StandardError, not Exception. A bare rescue
only catches StandardError and its subclasses; Exception also
covers things like pressing Ctrl + C (Interrupt), which should
normally stop the program.
The full structure: rescue, else, ensure
def process(value)
puts "start"
result = 100 / value
rescue ZeroDivisionError => e
puts "rescued: #{e.message}"
else
puts "no error, result #{result}"
ensure
puts "cleanup always runs"
end
process(4)
process(0)
start
no error, result 25
cleanup always runs
start
rescued: divided by 0
cleanup always runs
elseruns only if nothing was raised.ensureruns no matter what — use it for cleanup such as closing a file or a connection.
retry
retry re-runs the begin block — useful for flaky operations such as
network calls. Always limit the attempts:
attempts = 0
begin
attempts += 1
raise IOError, "network down" if attempts < 3
puts "succeeded on attempt #{attempts}"
rescue IOError
retry if attempts < 3
puts "giving up"
end
succeeded on attempt 3
Common mistakes
rescue Exception— it swallows Ctrl + C and system errors. RescueStandardErroror something more specific.- Rescuing and doing nothing (
rescue; end). The error vanishes, and you lose the information needed to fix the bug. At minimum, log it. retrywith no limit — an endless loop if the problem never clears.
Practice
Define an InvalidAge error. Write set_age(value) that raises it
with a helpful message when the value isn't between 0 and 150, and
rescue it when calling set_age(200).
Show solution
class InvalidAge < StandardError; end
def set_age(value)
raise InvalidAge, "age #{value} is out of range (0-150)" unless (0..150).cover?(value)
value
end
begin
set_age(200)
rescue InvalidAge => e
puts "Error: #{e.message}"
end
Error: age 200 is out of range (0-150)