Page Content

Tutorials

Exception In Ruby: Custom Classes And The Hierarchy

Exception in Ruby

Usually indicating that something went wrong during program execution, an exception is an object that represents an uncommon or exceptional condition. Errors are another name for exceptions in Ruby. The software often crashes or exits if an exception is triggered and not handled explicitly, disrupting the regular flow of execution.

Because Ruby’s exception system is a control structure, raising an exception shifts control from the site of failure to designated handling code. Ruby handles exceptions with the rescue clause and signals them with the Kernel method raise.

The Exception Hierarchy

Every exception object is an instance of either the Exception class or one of the numerous subclasses that it contains. It is critical to comprehend the hierarchy:

Exception: The base of the hierarchy. Exceptions that derive directly from Exception (like SystemExit or NoMemoryError) usually indicate significant or virtual machine faults and are generally not detectable with plain rescue.

StandardError: The majority of “normal” exceptions are subclasses of StandardError, which programmers should expect and manage. TypeError, RuntimeError, and ArgumentError are a few examples.

You can also read What Is The Command Line Crash Course Manipulation in Ruby

Raising Exceptions

An exception is raised using the Kernel#raise method (or its counterpart, Kernel#fail).

Code Example: Raising Exceptions


def hello(subject = "World") # <-- Assign a default value here
  # The subject will be "World" if no argument is passed,
  # so this validation check is now redundant but kept for clarity.
  if subject.to_s.empty?
    # This line only executes if someone explicitly passes "" or nil.
    raise ArgumentError, "`subject` is missing" 
  end
  
  puts "Hello #{subject}"
end

# 1. Calling with no argument (uses default "World")
hello 
# Expected Output: Hello World

# 2. Calling with a specific argument
hello("Rubyist")
# Expected Output: Hello Rubyist

# 3. Calling with a blank argument (will still raise the custom error)
# hello("") 
# => ArgumentError: `subject` is missing

Output

Hello World
Hello Rubyist

A new RuntimeError is created and raised with that string as the message if raise is called with a single string parameter. The exception class and an optional message are given in order to raise a particular kind of exception:

Custom Exceptions

By building a class that is a descendant of StandardError, you can establish a custom exception type.

# 1. Define the Custom Exception
class FileNotFound < StandardError
  # No body needed; it inherits all functionality from StandardError
end

# 2. The File Reading Method with Custom Error Logic
def read_file(path)
  # Short-circuit logic: If File.exist? is FALSE, the raise executes.
  File.exist?(path) || raise(FileNotFound, "File '#{path}' not found")
  
  # This line only runs if the File.exist? check passed (returned true).
  return File.read(path)
end

# --- Setup for Demonstration ---

# Create a temporary file for the SUCCESS case
File.write("existing.txt", "Hello from the file!")

# --- Demonstration ---

puts "--- SUCCESS CASE ---"
begin
  content = read_file("existing.txt")
  puts "Successfully read content: \"#{content.strip}\""
rescue StandardError => e
  # This rescue block should not be hit in the success case
  puts "Caught an unexpected error: #{e.message}"
end

puts "\n--- FAILURE CASE ---"
# Use a begin/rescue block to gracefully catch the expected error
begin
  read_file("missing.txt")
rescue FileNotFound => e
  puts "Caught expected error: #{e.class}"
  puts "Error Message: #{e.message}"
end

# Clean up the temporary file
File.delete("existing.txt")

Output

--- SUCCESS CASE ---
Successfully read content: "Hello from the file!"

--- FAILURE CASE ---
Caught expected error: FileNotFound
Error Message: File 'missing.txt' not found

You can also read Method Visibility In Ruby : Public, Private, And Protected

Handling Exceptions (///)

The begin/end block is used to catch exceptions, and assure, else, and rescue clauses are added as needed. You can skip the begin keyword if you specify these clauses immediately in a method (def), class, or module block.

Code Example: Basic Handling and Clauses

Where an error might occur, code is executed by a begin block. In the event that the begin block raises an exception, the code to execute is specified by the rescue clause.

def divide(x, y)
  begin
    return x/y
  rescue
    # A bare rescue catches StandardError and descendants
    puts "There was an error"
    return nil
  end
end

divide(10, 0) # ZeroDivisionError caught; prints "There was an error" 

Output

There was an error

Key Clauses and Components:

ClausePurposeExecution Timing
rescueExecutes the error handling code.Executes only if an exception matching the specified type is raised in the begin block.
elseExecutes code if no exception is raised in the begin block.Executes only if the begin block runs to completion normally.
ensureContains code that always runs, regardless of normal completion, explicit return, or unhandled exceptions.Executes before the result is returned, ensuring cleanup (like closing files) is performed.

Code Example: Specific Error Handling

It is up to you to decide which exceptions to save. Errors can be checked from top to bottom by stacking multiple rescue clauses.

def divide(x, y)
  begin
    z = x/y
  rescue ZeroDivisionError
    puts "Don't divide by zero!" # Handles x/0
    return nil # Exit the method after handling the error
  rescue TypeError
    puts "Division only works on numbers!" # Handles x/"a"
    return nil
  rescue => e # Catch all remaining StandardError subclasses
    puts "Caught unexpected error: #{e.class}"
    return nil
  else 
    puts "Successfully calculated result." # Runs if no exceptions are raised
    return z
  ensure
    puts "This code ALWAYS runs." # Runs whether successful or failed
  end
end

# --- Demonstration Calls ---

puts "\n--- Successful Call (x/y) ---"
result_success = divide(10, 2)
puts "Returned value: #{result_success}"

puts "\n--- ZeroDivisionError Call (x/0) ---"
result_zero = divide(10, 0)
puts "Returned value: #{result_zero}"

puts "\n--- TypeError Call (x/'a') ---"
result_type = divide(10, 'a')
puts "Returned value: #{result_type}"

puts "\n--- Unexpected Error (x/Object) ---"
# We'll use a class that doesn't define division for demonstration 
class SomeObject; end
result_other = divide(10, SomeObject.new)
puts "Returned value: #{result_other}"

Output

--- Successful Call (x/y) ---
Successfully calculated result.
This code ALWAYS runs.
Returned value: 5

--- ZeroDivisionError Call (x/0) ---
Don't divide by zero!
This code ALWAYS runs.
Returned value: 

--- TypeError Call (x/'a') ---
Division only works on numbers!
This code ALWAYS runs.
Returned value: 

--- Unexpected Error (x/Object) ---
Division only works on numbers!
This code ALWAYS runs.
Returned value: 

You can also read What Is Inheritance In Ruby With Practical Code Examples

Rerunning Code ()

When used in a rescue clause, the retry statement starts the enclosing begin block over from the beginning. This is helpful for temporary malfunctions (such as problems with network connectivity).

Code Example

This example code is intended to fail first, “fix” the issue in the rescue block, and then successfully try again:

def rescue_and_retry
  error_fixed = false 
  begin
    puts 'I am before the raise in the begin block.'
    raise 'An error has occurred!' unless error_fixed # Raises the first time
    puts 'I am after the raise in the begin block.'
  rescue
    puts 'An exception was thrown! Retrying...'
    error_fixed = true # Fixes the condition
    retry # Restarts the begin block
  end
  puts 'I am after the begin block.'
end

rescue_and_retry
# Output:
# I am before the raise in the begin block.
# An exception was thrown! Retrying...
# I am before the raise in the begin block.
# I am after the raise in the begin block.
# I am after the begin block. 

Output

I am before the raise in the begin block.
An exception was thrown! Retrying...
I am before the raise in the begin block.
I am after the raise in the begin block.
I am after the begin block.

Exception Information

To obtain comprehensive details, you can capture the exception object after it has been retrieved.

  • => error: Appends => variable_name to the rescue statement to store the exception object.
  • message: Returns a string providing human-readable details about the error.
  • backtrace: Returns an array of strings representing the call stack when the exception occurred.
  • $!: The global variable $! (or $ERROR_INFO if require 'English' is used) refers to the last exception object raised.
begin
  # Attempt risky operation: Division by zero. 
  result = 10 / 0 
rescue Exception => e
  puts "Caught an exception!"
  puts "------------------------"
  puts "Error Message: #{e.message}"
  puts "\nBacktrace:"
  puts e.backtrace.inspect 
end

Output

Caught an exception!
------------------------
Error Message: divided by 0

Backtrace:
["/tmp/Kf3jwYOfXa/main.rb:3:in 'Integer#/'", "/tmp/Kf3jwYOfXa/main.rb:3:in '<main>'"]

You can also read Applying Polymorphism In Ruby with Inheritance & Duck Typing

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index