Error Handling in Ruby
Proper error handling makes your applications robust and user-friendly. Ruby provides a clean syntax for handling exceptions.
Basic Exception Handling
ruby
1begin
2 # Code that might raise an exception
3 result = 10 / 0
4rescue
5 # Handle the exception
6 puts "An error occurred!"
7endCatching Specific Exceptions
ruby
1begin
2 # Try to open a file
3 file = File.open("nonexistent.txt")
4rescue Errno::ENOENT
5 puts "File not found!"
6rescue Errno::EACCES
7 puts "Permission denied!"
8rescue => e
9 puts "Unknown error: #{e.message}"
10endCommon Exception Classes
ruby
1# Ruby's exception hierarchy
2# Exception
3# ├── NoMemoryError
4# ├── ScriptError
5# │ ├── LoadError
6# │ └── SyntaxError
7# └── StandardError (default for rescue)
8# ├── ArgumentError
9# ├── IOError
10# ├── NameError
11# │ └── NoMethodError
12# ├── RuntimeError
13# ├── TypeError
14# └── ZeroDivisionError
15
16# Examples of different exceptions
17begin
18 nil.upcase
19rescue NoMethodError => e
20 puts "Method error: #{e.message}"
21end
22
23begin
24 Integer("hello")
25rescue ArgumentError => e
26 puts "Invalid argument: #{e.message}"
27end
28
29begin
30 "hello" + 5
31rescue TypeError => e
32 puts "Type error: #{e.message}"
33endThe Complete Rescue Block
ruby
1begin
2 # Try to do something risky
3 file = File.open("config.txt")
4 data = file.read
5
6rescue Errno::ENOENT => e
7 # Handle file not found
8 puts "File not found: #{e.message}"
9 data = "default value"
10
11rescue => e
12 # Handle any other error
13 puts "Error: #{e.class} - #{e.message}"
14 raise # Re-raise the exception
15
16else
17 # Runs if NO exception occurred
18 puts "File read successfully!"
19
20ensure
21 # ALWAYS runs (like finally in other languages)
22 file&.close
23 puts "Cleanup complete"
24endRaising Exceptions
ruby
1# Raise a RuntimeError
2raise "Something went wrong!"
3
4# Raise a specific exception
5raise ArgumentError, "Age must be positive"
6
7# Raise with backtrace
8raise StandardError.new("Error!").tap { |e| e.set_backtrace(caller) }
9
10# Common pattern in methods
11def divide(a, b)
12 raise ArgumentError, "Cannot divide by zero" if b == 0
13 a / b
14end
15
16begin
17 divide(10, 0)
18rescue ArgumentError => e
19 puts e.message
20endCustom Exception Classes
ruby
1# Define custom exceptions
2class ApplicationError < StandardError; end
3
4class ValidationError < ApplicationError
5 attr_reader :field
6
7 def initialize(message, field = nil)
8 super(message)
9 @field = field
10 end
11end
12
13class AuthenticationError < ApplicationError; end
14class AuthorizationError < ApplicationError; end
15
16# Using custom exceptions
17class User
18 def save
19 raise ValidationError.new("Email is required", :email) if email.blank?
20 raise ValidationError.new("Name is too short", :name) if name.length < 2
21 # ... save logic
22 end
23end
24
25# Handling custom exceptions
26begin
27 user.save
28rescue ValidationError => e
29 puts "Validation failed on #{e.field}: #{e.message}"
30rescue ApplicationError => e
31 puts "Application error: #{e.message}"
32endRetry
Automatically retry failed operations:
ruby
1attempts = 0
2
3begin
4 attempts += 1
5 puts "Attempt #{attempts}"
6
7 # Simulate network error
8 raise "Network error" if attempts < 3
9
10 puts "Success!"
11rescue
12 retry if attempts < 5
13 raise # Give up after 5 attempts
14endRetry with Exponential Backoff
ruby
1def fetch_with_retry(url, max_attempts: 5)
2 attempts = 0
3
4 begin
5 attempts += 1
6 response = Net::HTTP.get(URI(url))
7 return response
8
9 rescue Net::OpenTimeout, Net::ReadTimeout => e
10 if attempts < max_attempts
11 sleep_time = 2 ** attempts # 2, 4, 8, 16, 32 seconds
12 puts "Attempt #{attempts} failed, retrying in #{sleep_time}s..."
13 sleep(sleep_time)
14 retry
15 else
16 raise "Failed after #{max_attempts} attempts: #{e.message}"
17 end
18 end
19endException Handling in Methods
ruby
1# Inline rescue (use sparingly)
2result = potentially_failing_method rescue default_value
3
4# Better: explicit handling
5def find_user(id)
6 User.find(id)
7rescue ActiveRecord::RecordNotFound
8 nil
9end
10
11# Method-level rescue
12def process_data
13 # method body
14rescue => e
15 log_error(e)
16 raise
17endRails-Specific Error Handling
ruby
1# In controllers
2class UsersController < ApplicationController
3 rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
4 rescue_from ActionController::ParameterMissing, with: :bad_request
5
6 def show
7 @user = User.find(params[:id])
8 end
9
10 private
11
12 def record_not_found
13 render json: { error: "Record not found" }, status: :not_found
14 end
15
16 def bad_request(exception)
17 render json: { error: exception.message }, status: :bad_request
18 end
19end
20
21# In models
22class User < ApplicationRecord
23 def transfer_funds(amount, recipient)
24 transaction do
25 self.balance -= amount
26 recipient.balance += amount
27 save!
28 recipient.save!
29 end
30 rescue ActiveRecord::RecordInvalid => e
31 errors.add(:base, "Transfer failed: #{e.message}")
32 false
33 end
34endBest Practices
ruby
1# 1. Be specific about what you rescue
2# Bad
3rescue Exception # Catches EVERYTHING including SyntaxError
4
5# Good
6rescue StandardError # Catches most runtime errors
7
8# 2. Don't suppress exceptions silently
9# Bad
10begin
11 risky_operation
12rescue
13 # Silent failure - debugging nightmare!
14end
15
16# Good
17begin
18 risky_operation
19rescue => e
20 Rails.logger.error "Operation failed: #{e.message}"
21 Rails.logger.error e.backtrace.join("\n")
22end
23
24# 3. Use ensure for cleanup
25file = File.open("data.txt")
26begin
27 process(file)
28ensure
29 file.close # Always close the file
30end
31
32# 4. Prefer guard clauses over exceptions for expected conditions
33# Instead of:
34def process_user(user)
35 raise "User is nil" if user.nil?
36end
37
38# Do this:
39def process_user(user)
40 return if user.nil?
41 # process...
42endGood error handling is the difference between a crash and a graceful recovery!
