Exception Handling

Exception Class

When an exception occurs in Ruby code an instance of one of descendants of class Exception is used to carry data about the exception. Such an object has its class, may have a description and may include backtrace. Such an object may be a built-in Ruby object or a custom made class furnished with additional functionalities.

The most notable built-in subclasses of Exception are: SyntaxError, StandardError (default for rescue block), ArgumentError, NameError, NoMethodError, and RuntimeError (default for raise block).

It is possible to define a custom-cut exception class through subclassing of Exception class or one of its subclasses.

class MyCustomError < Exception
end

Raising

An exception in Ruby can be raised implicitly due to some underlying code defect or explicitly by a programmer in a manner decided by the programmer.

1/0 # ZeroDivisionError: divided by 0

raise "Nobody expects the Spanish inquisition!" # RuntimeError: Nobody expects the Spanish inquisition!

raise ZeroDivisionError, "Please, don't divide by zero!" # ZeroDivisionError: Please, don't divide by zero!

Rescuing

Once an exception occurs code execution stops unless it is rescued. The reserved keyword rescue can be used within a method or within a block.

def divide_by_zero
  1/0
rescue
  puts "Rescued!"
end

divide_by_zero # Rescued!

begin
  1/0
rescue
  puts "Rescued!"
end
# Rescued!

It is also possible to rescue from a specific subclass of Exception only.

def divide_by_zero
  1/0
rescue SyntaxError
  puts "Rescued!"
end

divide_by_zero # ZeroDivisionError: divided by 0

It is also possible to catch the exception object.

def divide_by_zero
  1/0
rescue => e
  p e
  puts "Rescued!"
end

divide_by_zero
# #<ZeroDivisionError: divided by 0>
# Rescued!

e (or other name set by the programmer) denotes an object that is an instance of one of the subclasses of the Exception class. In this particular case e is an istance of ZeroDivisionError class.

e.backtrace and e.backtrace_locations methods can be used to track traceback data.

Combining Rescue & Raise

We can use raise and rescue keywords within the same block.

def divide_by_zero
  raise ZeroDivisionError, "Please, don't divide by zero!"
rescue ZeroDivisionError => e
  puts e
  puts e.backtrace 
  puts "Rescued!"
end

divide_by_zero
# #<ZeroDivisionError: divided by 0>
# ["(pry):296:in `divide_by_zero'", "(pry):302:in ...] 
# Rescued!

Else

Within a method or a block in addition to the rescue keyword the keyword else can also be used. The code following else is only executed when no exception is raised.

Ensure

Within a method or a block in addition to the rescue and else keywords the keyword ensure can also be used. The code following ensure is always executed irrespective of whether any exception occured or not.

Retry

retry can be placed within rescue block to tell the program to retry the rescued block. This should be used with caution as not to create an infinite loop.