Exceptions and Error Handling
22.1 begin / rescue / else / ensure / retry
def fetch_data(url)
attempts = 0
begin
attempts += 1
response = HTTPClient.get(url)
rescue Timeout::Error, Errno::ECONNREFUSED => e
retry if attempts < 3 # re-run the begin block
warn "Failed after #{attempts} attempts: #{e.message}"
nil
rescue JSON::ParserError => e
raise DataError, "Invalid JSON from #{url}" # translates and chains (e.cause is set automatically)
else
response # runs only if no exception was raised
ensure
log("fetch attempted: #{url}") # always runs
end
end
def safe_divide(a, b) # method-level rescue (no begin needed)
a / b
rescue ZeroDivisionError
Float::INFINITY
end
value = Integer(input) rescue 0 # modifier rescue (use sparingly)22.2 Exception Hierarchy
A bare rescue catches StandardError. Never rescue Exception broadly (it catches Ctrl+C/Interrupt and SystemExit).
Exception
├── NoMemoryError, ScriptError (LoadError, NotImplementedError, SyntaxError)
├── SecurityError, SignalException (Interrupt), SystemExit, SystemStackError
└── StandardError ← rescue catches this by default
├── ArgumentError (UncaughtThrowError), EncodingError, FiberError
├── IOError (EOFError), IndexError (KeyError, StopIteration → ClosedQueueError)
├── LocalJumpError, NameError (NoMethodError), RangeError (FloatDomainError)
├── RegexpError, RuntimeError (FrozenError), ThreadError, TypeError, ZeroDivisionError
└── SystemCallError (Errno::ENOENT, Errno::EACCES, ...)22.3 Raising and Custom Exceptions
raise "Something went wrong" # RuntimeError
raise ArgumentError, "age must be positive"
raise ArgumentError.new("bad")
module MyApp
class Error < StandardError; end # base error for your library
class NotFoundError < Error
attr_reader :resource
def initialize(resource, msg = "#{resource} not found")
@resource = resource
super(msg)
end
end
class ValidationError < Error
def initialize(errors) = (@errors = errors; super("Validation failed: #{errors.join(', ')}"))
attr_reader :errors
end
end
begin
raise MyApp::NotFoundError.new("User 42")
rescue MyApp::Error => e
puts "#{e.class}: #{e.message}"
puts e.backtrace.first(3)
puts e.full_message(highlight: false)
end22.4 Error Handling Practices
Rescue specific exceptions. Keep begin blocks small. Don't use exceptions for normal control flow.
Define a library base error (MyLib::Error). Wrap low-level errors while preserving cause.
ensure for cleanup, or better, block-based resource methods (File.open(...) { }).
Use Kernel#exit / abort("msg") in CLIs. Use at_exit hooks. Use Timeout.timeout cautiously (prefer native timeouts in client libraries).
Result objects are an alternative for expected failures (the dry-monads gem: Success/Failure).