Skip
Arish's avatar

15. Error Handling


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!"
7end

Catching 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}"
10end

Common 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}"
33end

The 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"
24end

Raising 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
20end

Custom 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}"
32end

Retry

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
14end

Retry 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
19end

Exception 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
17end

Rails-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
34end

Best 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...
42end

Good error handling is the difference between a crash and a graceful recovery!