Related Topics
Exceptions in Ruby
An exception is an event that disrupts the normal flow of the program. Ruby helps us to handle these events in a way that is suitable to our needs.
We can handle these events by declaring the code in between begin/rescue block to catch an exception.
Ways of handling exceptions
1.The General Way
begin
# an execution that may fail
rescue
# something to execute in case of failure
End
Here the code in rescue block is executed by default whenever an exception is raised in the begin block
2. Checking for Exceptions
begin
# an execution that may fail
rescue StandardError
# Executes only when the begin block code raises a standardError
end
In this case the rescue block is executed only when a standardError is raised. All the other exceptions go unhandled.
3. Storing exception information
begin
# an execution that may fail
rescue StandardError => error
# Executes only when the begin block code raises a standardError
puts error.message #Prints the error message.
end
In this case, after catching the exception, all the data related to the exception is written into the error object.
4. The retry command
begin
#here goes your code
rescue StandardError => e
#if you want to retry the code
retry
end
When the compiler encounters the retry command, ruby automatically re-executes the code block present in begin.
5. The ensure command
begin
#here goes your code
rescue StandardError => e
puts e.message
ensure
#This block is executed regardless of the way the exception is handled
puts “This is from ensure block!”
end
The code written in the ensure block is always executed regardless of the way the exception is handled.
6. The raise statement
We can raise an exception using the raise statement.
def hello(subject)
raise ArgumentError, "`subject` is missing" if subject.to_s.empty?
puts "Hello #{subject}"
end
hello # => ArgumentError: `subject` is missing
hello("Simone") # => "Hello Simone”
When the interpreter encounters the raise statement it throws an exception
Handling multiple exceptions
We can handle multiple exceptions using two ways
Declaring multiple exceptions in a single rescue statement.
begin
# an execution that may fail
rescue StandardError, ArgumentError, Exception
# Executes when the begin block code raises a StandardError and Argument Error
End
7. Declaring multiple rescue statements
begin
# an execution that may fail
rescue StandardError, ArgumentError
# Executes only when the begin block code raises a standardError
rescue ArgumentError
# Executes only when the begin block code raises an ArgumentError
End
8. Raising a Custom Exception
Any class that extends an Exception or a subclass of an exception is said to be a custom exception.
Here is an example demonstrating the way in which we can declare a custom exception.
# Defines a new custom exception called FileNotFound
class FileNotFound < StandardError
end
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 file
Raising an exception
To raise an exception useKernel#raise
passing the exception class and/or message:
raise StandardError # raises a StandardError.new
raise 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 aRuntimeError
:
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 missing
hello("Simone") # => "Hello Simone"
Handling multiple exceptions
You can handle multiple errors in the samerescue
declaration:
begin
# an execution that may fail
rescue FirstError, SecondError => e
# do something if a FirstError or SecondError occurs
end
You can also add multiplerescue
declarations:
begin
# an execution that may fail
rescue FirstError => e
# do something if a FirstError occurs
rescue SecondError => e
# do something if a SecondError occurs
rescue => e
# do something if a StandardError occurs
end
The order of therescue
blocks is relevant: the first match is the one executed. Therefore, if you putStandardError
as the first condition and all your exceptions inherit fromStandardError
, then the otherrescue
statements will never be executed.
begin
# an execution that may fail
rescue => e
# this will swallow all the errors
rescue FirstError => e
# do something if a FirstError occurs
rescue SecondError => e
# do something if a SecondError occurs
end
Some blocks have implicit exception handling likedef
,class
, andmodule
. These blocks allow you to skip the begin statement.
def foo
...
rescue CustomError
...
ensure
...
end
Creating a custom exception type
A custom exception is any class that extendsException
or a subclass ofException
.
In general, you should always extendStandardError
or a descendant. TheException
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 FileNotFound
class FileNotFound < StandardError
end
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 file
It's common to name exceptions by adding theError
suffix at the end:
- ConnectionError
- DontPanicError
However, when the error is self-explanatory, you don't need to add theError
suffix because would be redundant:
FileNotFound
vsFileNotFoundError
DatabaseExploded
vsDatabaseExplodedError
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)
end
end
Raising the exception:
raise CustomError.new(true)
Catching the exception and accessing the additional information provided:
begin
# do stuff
rescue CustomError => e
retry if e.safe_to_retry
end