Ruby on Rails Part 4 - Exception Handling
Table of content
Exception Handling
retry
raise
ensure
else
catch and throw
Exception classes
Exception Handling
Enclose the code that could raise an exception with a begin/end block and use rescue clauses to tell Ruby the types of exceptions that you want to handle. The syntax for exception handling :
begin
#- statements
rescue OneTypeOfException
#-
handle the exception
rescue AnotherTypeOfException
#-
handle the exception
else
# Other exceptions
ensure
# ensure block is always executed
end
Everything from begin to rescue is protected in the block.
If an exception occurs during the execution of this block of code, control is passed to the block between rescue and end.
For each rescue clause in the begin block, Ruby compares the raised Exception against each of the parameters of the rescue block one by one.
The correct match is the exception named in the rescue clause is the same as the type of the currently thrown exception, or is a superclass of that exception.
In an event that an exception does not match any of the error types specified, it uses the else clause after all the rescue clauses.
begin
i = 5/0
rescue
puts "Division by zero not allowed"
end
Output
$ ruby exception_handling.rb
Division by zero not allowed
$
retry
You can capture an exception using rescue block and then use retry statement to execute begin block from the beginning.The retry is illustrated as below.
begin
# Exceptions raised by the code here
# and be caught by the following rescue clause
rescue
# This block captures all types of exceptions
retry # Thr retry moves control to the beginning of begin
end
In the below code, following is the flow of process: An exception occurred at opening of the file due to non_existent_file name, rescue captures the exception. In the rescue the fname was re-assigned to a existing_file name. With retry the control goes to the start of the begin block. This time file opens successfully and continues with the code flow.
If the retry statement also raises an exception then this will trigger an infinite loop. So be careful while using retry command.
fname = '/non_existent_file'
begin
file = open(fname)
if file
puts "File opened successfully"
end
rescue
fname = "existing_file"
retry
end
raise
Raise statement is used to raise/throw an exception. This is similar to the ‘throws’ keyword in Java. The statement has multiple forms.
raise # this raises a generic runtime error
OR
raise "Error Message"
# this raises a generic runtime error with a custom message
OR
raise ExceptionType, "Error Message" # this raises an exception with a custom message
OR
raise ExceptionType, "Error Message" condition # raises an exception with a custom message if the condition is true else raise no exception
The first form simply re-raises the current exception (or a RuntimeError if there is no current exception). This is used in exception handlers that need to intercept an exception before passing it on.
The second form creates a new RuntimeError exception, setting its message to the given string. This exception is then raised up the call stack.
The third form uses the first argument to create an exception and then sets the associated message to the second argument.
The fourth form is similar to the third form but you can add any conditional statement like unless to raise an exception. If the condition is true then raise the exception.
begin
puts 'Before raise.'
raise 'Raising an error'
puts 'After raise'
rescue Exception => e
puts 'In rescue block'
puts e.class
end
puts 'Outside of begin block'
Output
$ ruby raise_exception_example.rb
Before raise.
In rescue block
RuntimeError
Outside of begin block
$
Raise with custom message
begin
raise 'A test exception'
rescue Exception => e
puts e.message
puts e.backtrace
end
Output
$ ruby raise_custom_message.rb
A test exception
a.rb:2:in `<main>'
$
ensure
Sometimes, you need to guarantee that some processing is done at the end of a block of code, regardless of whether an exception was raised. This is similar to the finally block in java. The syntax for ensure :
begin
#.. process
#.. raise exception
rescue
#.. handle error
ensure
#.. finally ensure execution
#.. This block always executes
end
begin
raise 'A test exception.'
rescue Exception => e
puts e.message
puts e.backtrace.inspect
ensure
puts "Ensuring execution"
end
Output
$ ruby a.rb
A test exception.
["a.rb:2:in `<main>'"]
Ensuring execution
$
else
If the else clause is present, it goes after the rescue clauses and before any ensure. The body of an else clause is executed only if no exceptions are raised by the main body of code.
# Syntax of else in exception handling
begin
#.. process
#.. raise exception
rescue
#.. handle error
else
#.. executes if there is no exception
ensure
#.. finally ensure execution
#.. This will always execute
end
Example of else block.
begin
puts "I'm not raising exception"
rescue Exception => e
puts e.message
puts e.backtrace.inspect
else
puts "Congratulations-- no errors!"
ensure
puts "Ensuring execution"
end
Output
$ ruby else_block_example.rb
I'm not raising exception
Congratulations-- no errors!
Ensuring execution
$
catch and throw
throw: Throws the control to a catch block with the same label .
catch: Catches the control thrown by a throw command with same label.
catch and throw are mainly used to jump of out multiple nested constructs. break can only break out of a single loop at once, but throw can break out of all the loops.
Syntax of throw and catch :
catch :label_name do
# matching catch will be executed when the throw block is encountered
throw :label_name condition
# thrown only if the condition is true # this block will not be executed
end
Example of catch - throw :
flag = 0
catch :labelName do
puts("This will not be executed") if flag == 1
for i in 0..100000
for j in 0..100000
for k in 0..100000
for l in 0..100000
for m in 0..100000
for n in 0..100000
for o in 0..100000
for p in 0..100000
flag = 1
throw :labelName if p == 0
puts "This statement and everything below will not be executed"
end
end
end
end
end
end
end
end
end
puts("This will be executed") if flag == 1
Output
$ ruby catch_throw_example.rb
This will be executed
$
Exception classes
The Exception class is the parent of all exception classes. It can hold all other exceptions. The other main types of exception classes are as follows:
Interrupt
NoMemoryError
SignalException
ScriptError
StandardError
SystemExit
All these classes are the subclasses of the parent Exception class. We can create our own exception classes, but they need to be subclass of either class Exception or one of its descendants.
Create my own exception class by inheriting from the Exception class :
class MyCustomException < Exception
attr_reader :reason
def initialize(reason)
@reason = reason
end
end
begin
# Exception raised in the following line
5/0
rescue
# The exception was raised to a higher level using the raise keyword
raise MyCustomException.new("Divide by 0").reason
end
Output
$ ruby custom_exception_class.rb
custom_exception_class.rb:13:in `rescue in <main>': Divide by 0 (RuntimeError)
from custom_exception_class.rb:8:in `<main>'
$