# Basic exception handling
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "Error: #{e.message}"
result = nil
end
# Multiple rescue clauses
begin
# Risky operation
file = File.open('nonexistent.txt')
rescue Errno::ENOENT => e
puts "File not found: #{e.message}"
rescue IOError => e
puts "IO Error: #{e.message}"
rescue StandardError => e
puts "Standard error: #{e.message}"
ensure
# Always runs, even if exception occurs
file&.close
end
# Rescue and retry
attempts = 0
begin
attempts += 1
# Network call that might fail
response = HTTParty.get('https://api.example.com/data')
rescue Net::OpenTimeout, Net::ReadTimeout => e
if attempts < 3
sleep(2 ** attempts) # Exponential backoff
retry
else
raise # Re-raise after max attempts
end
end
# Inline rescue
value = risky_method rescue default_value
user = User.find(params[:id]) rescue nil
# Rescue in methods (implicit begin)
def fetch_user(id)
User.find(id)
rescue ActiveRecord::RecordNotFound
User.new(name: 'Guest')
end
# Raising exceptions
raise "Something went wrong"
raise ArgumentError, "Invalid argument"
raise ArgumentError.new("Invalid argument")
# Re-raising with modification
begin
# Operation
rescue StandardError => e
raise StandardError, "Failed: #{e.message}", e.backtrace
end
# Custom exception classes
class ApplicationError < StandardError; end
class AuthenticationError < ApplicationError; end
class AuthorizationError < ApplicationError; end
class ValidationError < ApplicationError
attr_reader :errors
def initialize(errors)
@errors = errors
super("Validation failed: #{errors.join(', ')}")
end
end
# Usage
def authenticate_user(credentials)
raise AuthenticationError, "Invalid credentials" unless valid?(credentials)
# Authentication logic
end
def authorize_user(user, action)
raise AuthorizationError, "Not authorized to #{action}" unless user.can?(action)
# Authorization logic
end
def validate_input(data)
errors = []
errors << "Name is required" if data[:name].blank?
errors << "Email is invalid" unless data[:email]&.match?(/@/)
raise ValidationError.new(errors) if errors.any?
end
# Catching custom exceptions
begin
validate_input(params)
rescue ValidationError => e
render json: { errors: e.errors }, status: :unprocessable_entity
rescue AuthenticationError => e
render json: { error: e.message }, status: :unauthorized
rescue AuthorizationError => e
render json: { error: e.message }, status: :forbidden
end
# Exception with custom data
class APIError < StandardError
attr_reader :status_code, :response_body
def initialize(message, status_code:, response_body: nil)
@status_code = status_code
@response_body = response_body
super(message)
end
end
# Usage
raise APIError.new(
"API request failed",
status_code: 500,
response_body: { error: "Internal error" }
)
# Exception translation pattern
class ExternalAPIClient
class APIError < StandardError; end
class NotFoundError < APIError; end
class RateLimitError < APIError; end
def fetch_data(id)
response = HTTParty.get("https://api.example.com/items/#{id}")
case response.code
when 200
response.parsed_response
when 404
raise NotFoundError, "Item #{id} not found"
when 429
raise RateLimitError, "Rate limit exceeded"
else
raise APIError, "Request failed with status #{response.code}"
end
rescue HTTParty::Error => e
raise APIError, "HTTP error: #{e.message}"
end
end
# Circuit breaker pattern
class CircuitBreaker
class CircuitOpenError < StandardError; end
def initialize(threshold: 5, timeout: 60)
@failure_count = 0
@threshold = threshold
@timeout = timeout
@last_failure_time = nil
@state = :closed
end
def call
raise CircuitOpenError if open?
yield
on_success
rescue StandardError => e
on_failure
raise
end
private
def open?
@state == :open && Time.now - @last_failure_time < @timeout
end
def on_success
@failure_count = 0
@state = :closed
end
def on_failure
@failure_count += 1
@last_failure_time = Time.now
@state = :open if @failure_count >= @threshold
end
end
# Safe navigation with exceptions
def safe_find(model, id)
model.find(id)
rescue ActiveRecord::RecordNotFound
nil
end
# Exception logging
begin
dangerous_operation
rescue StandardError => e
Rails.logger.error("Operation failed: #{e.class.name} - #{e.message}")
Rails.logger.error(e.backtrace.join("\n"))
# Report to error tracking service
Sentry.capture_exception(e)
raise
end
# Suppressing exceptions (use sparingly!)
suppress(Errno::ENOENT) do
File.delete('tmp/cache.dat')
end
Ruby's exception handling uses begin/rescue/ensure/end. I rescue specific exceptions before general ones. rescue catches exceptions; ensure runs cleanup code always. retry attempts operation again; raise re-raises exceptions. Custom exceptions inherit from StandardError. Multiple rescue clauses handle different error types. Exception messages and backtraces aid debugging. I use inline rescue for simple cases—value = risky_method rescue default_value. Rescue modifiers catch and suppress errors—useful but potentially dangerous. Exception handling enables graceful degradation. Proper error management improves robustness and user experience. Understanding Ruby's exception hierarchy guides rescue strategies.