Exceptions
Creating a custom exception type
Section titled “Creating a custom exception type”A custom exception is any class that extends Exception or a subclass of Exception.
In general, you should always extend StandardError or a descendant. The Exception family are usually for virtual-machine or system errors, rescuing them can prevent a forced interruption from working as expected.
# Defines a new custom exception called FileNotFoundclass FileNotFound < StandardErrorend
def read_file(path) File.exist?(path) || raise(FileNotFound, "File #{path} not found") File.read(path)end
read_file("missing.txt") #=> raises FileNotFound.new("File `missing.txt` not found")read_file("valid.txt") #=> reads and returns the content of the fileIt’s common to name exceptions by adding the Error suffix at the end:
ConnectionErrorDontPanicError
However, when the error is self-explanatory, you don’t need to add the Error suffix because would be redundant:
FileNotFoundvsFileNotFoundErrorDatabaseExplodedvsDatabaseExplodedError
Handling multiple exceptions
Section titled “Handling multiple exceptions”You can handle multiple errors in the same rescue declaration:
begin # an execution that may failrescue FirstError, SecondError => e # do something if a FirstError or SecondError occursendYou can also add multiple rescue declarations:
begin # an execution that may failrescue FirstError => e # do something if a FirstError occursrescue SecondError => e # do something if a SecondError occursrescue => e # do something if a StandardError occursendThe order of the rescue blocks is relevant: the first match is the one executed. Therefore, if you put StandardError as the first condition and all your exceptions inherit from StandardError, then the other rescue statements will never be executed.
begin # an execution that may failrescue => e # this will swallow all the errorsrescue FirstError => e # do something if a FirstError occursrescue SecondError => e # do something if a SecondError occursendSome blocks have implicit exception handling like def, class, and module. These blocks allow you to skip the begin statement.
def foo ...rescue CustomError ...ensure ...endHandling an exception
Section titled “Handling an exception”Use the begin/rescue block to catch (rescue) an exception and handle it:
begin # an execution that may failrescue # something to execute in case of failureendA rescue clause is analogous to a catch block in a curly brace language like C# or Java.
A bare rescue like this rescues StandardError.
Note: Take care to avoid catching Exception instead of the default StandardError. The Exception class includes SystemExit and NoMemoryError and other serious exceptions that you usually don’t want to catch. Always consider catching StandardError (the default) instead.
You can also specify the exception class that should be rescued:
begin # an excecution that may failrescue CustomError # something to execute in case of CustomError # or descendantendThis rescue clause will not catch any exception that is not a CustomError.
You can also store the exception in a specific variable:
begin # an excecution that may failrescue CustomError => error # error contains the exception puts error.message # provide human-readable details about what went wrong. puts error.backtrace.inspect # return an array of strings that represent the call stackendIf you failed to handle an exception, you can raise it any time in a rescue block.
begin #here goes your coderescue => e #failed to handle raise eendIf you want to retry your begin block, call retry:
begin #here goes your coderescue StandardError => e #for some reason you want to retry you code retryendYou can be stuck in a loop if you catch an exception in every retry. To avoid this, limit your retry_count to a certain number of tries.
retry_count = 0begin # an excecution that may failrescue if retry_count < 5 retry_count = retry_count + 1 retry else #retry limit exceeds, do something else endYou can also provide an else block or an ensure block. An else block will be executed when the begin block completes without an exception thrown. An ensure block will always be executed. An ensure block is analogous to a finally block in a curly brace language like C# or Java.
begin # an execution that may failrescue # something to execute in case of failureelse # something to execute in case of successensure # something to always executeendIf you are inside a def, module or class block, there is no need to use the begin statement.
def foo ...rescue ...endRaising an exception
Section titled “Raising an exception”To raise an exception use Kernel#raise passing the exception class and/or message:
raise StandardError # raises a StandardError.newraise StandardError, "An error" # raises a StandardError.new("An error")You can also simply pass an error message. In this case, the message is wrapped into a RuntimeError:
raise "An error" # raises a RuntimeError.new("An error")Here’s an example:
def hello(subject) raise ArgumentError, "`subject` is missing" if subject.to_s.empty? puts "Hello #{subject}"end
hello # => ArgumentError: `subject` is missinghello("Simone") # => "Hello Simone"Adding information to (custom) exceptions
Section titled “Adding information to (custom) exceptions”It may be helpful to include additional information with an exception, e.g. for logging purposes or to allow conditional handling when the exception is caught:
class CustomError < StandardError attr_reader :safe_to_retry
def initialize(safe_to_retry = false, message = 'Something went wrong') @safe_to_retry = safe_to_retry super(message) endendRaising the exception:
raise CustomError.new(true)Catching the exception and accessing the additional information provided:
begin # do stuffrescue CustomError => e retry if e.safe_to_retryendRemarks
Section titled “Remarks”An exception is an object that represents the occurrence of an exceptional condition. In other words, it indicates that something went wrong.
In Ruby, exceptions are often referred to as errors. That’s because the base Exception class exists as a top-level exception object element, but user-defined execution exceptions are generally StandardError or descendants.