Contents

Exceptions

Ruby Exceptions

In programming, predicting errors and handling them effectively is crucial. Exceptions are errors that occur during runtime and disrupt the normal flow of a program. Handling exceptions properly ensures a program continues to operate even in unexpected situations.

What is an Exception?

An exception is an unexpected event that occurs during the execution of a program, disrupting its normal flow. It provides a way to handle error scenarios without stopping the entire program.

Errors vs. Exceptions

Errors:

  • Unexpected issues that arise during program execution.
  • Cannot be handled directly.
  • All errors are considered exceptions.

Exceptions:

  • Unexpected events during runtime.
  • Can be managed using begin-rescue blocks.
  • Not all exceptions are errors.
Traditional Exception Handling Approach

In the traditional approach, errors were managed using return codes. Methods would return specific values indicating failure, which would be propagated through calling routines until a function took responsibility. This made error management complex and hard to maintain.

Ruby addresses this issue by using an exception class that packages information about errors into an object. This object is passed through the calling stack until appropriate code is found to handle the error.

Exception Class & Its Hierarchy

Ruby has a built-in hierarchy of exception classes. Most exceptions are subclasses of StandardError, which represents general errors in Ruby programs. More serious exceptions fall under other classes. Users can also create their own exceptions by subclassing StandardError or its descendants. Every exception object contains a message and a stack trace.

Example of an Exception

				
					# Ruby program to illustrate an exception 

# defining two integer values
num1 = 14
num2 = 0

# attempting to divide by zero
result = num1 / num2

puts "The result is: #{result}"

				
			

Runtime Error:

				
					source_file.rb:6:in `/': divided by 0 (ZeroDivisionError)
    from source_file.rb:6:in `<main>'

				
			

Explanation

In the example above, dividing 14 by 0 triggers a ZeroDivisionError.

Creating User-Defined Exceptions

Ruby uses the raise method to create exceptions, which become instances of the Exception class or its subclasses. The rescue clause is used to handle these exceptions.

Example of User-Defined Exception

				
					# Ruby program to create a user-defined exception

# defining a method
def raise_exception
  puts 'This is before the exception is raised!'
  
  # using raise to create an exception
  raise 'Custom Exception Raised'

  puts 'This line will not be executed.'
end

# calling the method
raise_exception

				
			

Output:

				
					This is before the exception is raised!
source_file.rb:6:in `raise_exception': Custom Exception Raised (RuntimeError)
    from source_file.rb:10:in `<main>'

				
			

Handling Exceptions with rescue

You can handle exceptions in Ruby using begin-rescue blocks. This structure allows the program to continue running after handling an exception.

Example of Handling Exception

				
					# Ruby program to create and handle a user-defined exception

# defining a method
def raise_and_rescue
  begin
    puts 'This is before the exception is raised!'
    
    # using raise to create an exception
    raise 'Custom Exception Raised!'

    puts 'This line will not be executed.'

  # using rescue to handle the exception
  rescue
    puts 'Exception handled!'
  end
  
  puts 'Outside of the begin block!'
end

# calling the method
raise_and_rescue

				
			

Output:

				
					This is before the exception is raised!
Exception handled!
Outside of the begin block!

				
			

Ruby Exception Handling

Exception handling in Ruby provides a way to manage unexpected events or errors that occur during the execution of a program. These errors can disrupt the normal flow of a program and are managed using various statements like begin, rescue, raise, ensure, and more. Ruby also provides an Exception class, which has different methods to handle these errors.

Syntax of Exception Handling in Ruby

The code block where an exception might be raised is enclosed in a begin and end block. The rescue clause is used to manage the exception.

Basic Syntax:

				
					begin
  # Code where an exception might be raised
rescue
  # Code to handle the exception
end

				
			

Example of Exception Handling

				
					# Ruby program to handle an exception

# defining a method
def raise_and_rescue
  begin
    puts 'This is before an exception occurs!'

    # raising an exception
    raise 'An Exception Occurred!'

    puts 'This will not be printed'
  rescue
    puts 'Exception handled successfully!'
  end

  puts 'Outside the begin block!'
end

# calling the method
raise_and_rescue

				
			

Output:

				
					This is before an exception occurs!
Exception handled successfully!
Outside the begin block!

				
			

Explanation: In this example, an exception is raised in the begin block using raise, which interrupts the program’s flow. The rescue block then catches and handles the exception, allowing the program to continue executing.

Note

  • You can use multiple rescue clauses to handle different exceptions. If an exception is not handled by the first rescue, the next one is tried. If no rescue matches, or if an exception occurs outside the begin block, Ruby searches up the call stack for an exception handler.
Statements Used in Exception Handling

1. retry Statement: The retry statement re-executes the code from the beginning of the begin block after catching an exception.

Syntax:

				
					begin
  # Code where an exception might be raised
rescue
  # Code to handle the exception
  retry
end

				
			

Note: Be cautious while using retry as it can lead to an infinite loop if not properly managed.

2. raise Statement: The raise statement is used to raise an exception in Ruby.

Syntax:

  • raise: Re-raises the current exception.
  • raise "Error Message": Creates a RuntimeError with the given message.
  • raise ExceptionType, "Error Message": Creates an exception of the specified type with the given message.

Example:

				
					# Ruby program demonstrating the use of raise statement

begin
  puts 'This is before an exception occurs!'

  # Raising an exception
  raise 'An Exception Occurred!'

  puts 'This will not be printed'
end

				
			

Output:

				
					This is before an exception occurs!
An Exception Occurred!

				
			

3. ensure Statement: The ensure block always executes regardless of whether an exception is raised or rescued. It is placed after the rescue block.

Syntax:

				
					begin
  # Code where an exception might be raised
rescue
  # Code to handle the exception
ensure
  # Code that always executes
end

				
			

Output:

				
					# Ruby program demonstrating the use of the ensure statement

begin
  # Raising an exception
  raise 'An Exception Occurred!'
  puts 'This will not be printed'
rescue
  puts 'Exception handled!'
ensure
  puts 'Ensure block executed'
end

				
			

Output:

				
					Exception handled!
Ensure block executed

				
			

4. else Statement: The else block executes only if no exception is raised in the begin block. It is placed between the rescue and ensure blocks.

Syntax:

				
					begin
  # Code where an exception might be raised
rescue
  # Code to handle the exception
else
  # Code that executes if no exception is raised
ensure
  # Code that always executes
end

				
			

Example:

				
					# Ruby program demonstrating the use of the else statement

begin
  puts 'No exception is raised here'
rescue
  puts 'Exception handled!'
else
  puts 'Else block executed because no exception was raised'
ensure
  puts 'Ensure block executed'
end

				
			

Output:

				
					No exception is raised here
Else block executed because no exception was raised
Ensure block executed

				
			

5. catch and throw Statements:catch and throw provide a lightweight mechanism for managing errors and jumps in Ruby. The catch block works normally until it encounters a throw. When a throw is encountered, Ruby looks for the matching catch block with the same label.

Syntax:

				
					catch :label_name do
  # Code block
  throw :label_name if condition
end

				
			

Example:

				
					# Ruby program to illustrate Class Variables

class Library
  # Class variable
  @@book_count = 0

  def initialize(title)
    # Instance variable
    @title = title
  end

  def display_title
    puts "Book Title: #{@title}"
  end

  def add_book
    # Increment the class variable
    @@book_count += 1
    puts "Total books: #@@book_count"
  end
end

# Creating objects
book1 = Library.new("The Art of Ruby")
book2 = Library.new("Ruby on Rails Guide")

# Calling methods
book1.display_title
book1.add_book

book2.display_title
book2.add_book

				
			

Input:

				
					Enter a number: 1

				
			

Output:

				
					1
				
			

Input:

				
					Enter a number: !
				
			

Output:

				
					nil
				
			

Catch and Throw Exception In Ruby

In Ruby, an exception is an object that belongs to the Exception class or one of its subclasses. An exception occurs when a program reaches an undefined or unexpected state. At this point, Ruby doesn’t know how to proceed, so it raises an exception. This can happen automatically or be done manually by the programmer.

The catch and throw keywords in Ruby provide a way to handle exceptions, similar to how raise and rescue work. The throw keyword generates an exception, and when it is encountered, the program’s control flow jumps to the corresponding catch statement.

Syntax

				
					catch :label_name do
  # Code block that runs until a throw is encountered
  throw :label_name if condition
  # This block will not execute if throw is encountered
end
                                                                                                                                                                                                                                  
				
			

The catch block is used to jump out of nested code, and it will continue executing normally until a throw statement is encountered.

Example: Basic catch and throw

				
					# Ruby Program using Catch and Throw for Exception Handling
result = catch(:divide) do
  # Code block similar to 'begin'
  number = rand(2)
  throw :divide if number == 0
  number # set result = number if no exception is thrown
end

puts result

				
			

Output: If the random number is 0, the output will be empty since the throw :divide statement will execute and jump to the catch block. If the random number is 1, the output will be:

				
					1
				
			

Explanation: If the random number is 0, throw :divide is executed, and nothing is returned to the catch block, resulting in an empty output. If the number is 1, the exception is not thrown, and the result variable is set to 1.

Example:

In the previous example, when the exception was thrown, the variable result was set to nil. We can change this behavior by providing a default value to the throw keyword.

				
					# Ruby Program using Catch and Throw with a Default Value
result = catch(:divide) do
  # Code block similar to 'begin'
  number = rand(2)
  throw :divide, 10 if number == 0
  number # set result = number if no exception is thrown
end

puts result

				
			

Output:

				
					1
				
			

Exception Handling in Ruby

In Ruby, a literal is any constant value that can be assigned to a variable. We use literals whenever we type an object directly into the code. Ruby literals are similar to those in other programming languages, with some differences in syntax and functionality.

Types of Ruby Literals

Ruby supports various types of literals:

1. Booleans and nil
2. Numbers
3. Strings
4. Symbols
5. Ranges
6. Arrays
7. Hashes
8. Regular Expressions

1. Booleans and nil: Booleans are constants that represent the truth values true and false. nil is another constant that represents an “unknown” or “empty” value, and it behaves similarly to false in conditional expressions.

Example:

				
					raise exception_type, "exception message" if condition

				
			

Output:

				
					true
false
false

				
			

When a raise statement is executed, it invokes the rescue block. By default, raise raises a RuntimeError.

Example 1: Using raise Statement

				
					# Ruby program to demonstrate the use of raise statement
begin
  puts 'This is Before Exception Arises!'
  
  # Using raise to create an exception
  raise 'Exception Created!'

  puts 'After Exception'
end

				
			

Output:

				
					This is Before Exception Arises!
Exception Created!

				
			

Ruby Exception Handling in Threads

Threads in Ruby can also encounter exceptions. By default, only exceptions that arise in the main thread are handled, while exceptions in other threads cause those threads to terminate. The behavior of threads when an exception arises is controlled by the abort_on_exception method, which is set to false by default.

If abort_on_exception is false, an unhandled exception will abort the current thread but allow other threads to continue running. The setting can be changed by using Thread.abort_on_exception = true or by setting $DEBUG to true. Additionally, Ruby provides the ::handle_interrupt method for handling exceptions asynchronously within threads.

Example 1: Exception in Threads

				
					# Ruby program to demonstrate exception handling in threads

#!/usr/bin/ruby  

threads = []
4.times do |value|
  threads << Thread.new(value) do |i|
    # Raise an error when i equals 2
    raise "An error occurred!" if i == 2
    print "#{i}\n"
  end
end

threads.each { |t| t.join }

				
			

Output:

				
					0
3
1
main.rb:11:in `block (2 levels) in <main>': An error occurred! (RuntimeError)

				
			

Note: The Thread.join method waits for a specific thread to finish. When a Ruby program ends, all threads are terminated, regardless of their states.

Saving Exceptions in Threads

In the following example, we handle exceptions within threads to prevent program termination and continue processing other threads.

Example 2: Handling Exceptions in Threads

				
					# Ruby program to handle exceptions in threads

#!/usr/bin/ruby  

threads = []

5.times do |value|
  threads << Thread.new(value) do |i|
    raise "An error occurred!" if i == 3
    print "#{i}\n"
  end
end

threads.each do |t|
  begin
    t.join
  rescue RuntimeError => e
    puts "Exception caught: #{e.message}"
  end
end

				
			

Output:

				
					0
1
4
2
Exception caught: An error occurred!

				
			
Using abort_on_exception

Setting abort_on_exception to true will terminate the entire program as soon as an exception occurs in any thread. Once the thread with the exception is terminated, no further output will be generated.

Example 3: Using abort_on_exception

				
					# Ruby program to demonstrate thread termination on exception

#!/usr/bin/ruby  

# Set abort_on_exception to true
Thread.abort_on_exception = true

threads = []

5.times do |value|
  threads << Thread.new(value) do |i|
    raise "An error occurred!" if i == 3
    print "#{i}\n"
  end
end

# Use Thread.join to wait for threads to finish
threads.each { |t| t.join }

				
			

Output:

				
					0
1
2
main.rb:12:in `block (2 levels) in <main>': An error occurred! (RuntimeError)