Learn Ruby on Rails Book

Exception handling in Ruby

Exceptions in Ruby

In Ruby Exceptions are created using raise command.

1begin
2  raise "boom"
3end

If we execute that code then we will see the error.

1 boom (RuntimeError)

Notice that Ruby is saying that it is a RuntimeError. Here is Ruby's official documentation about RuntimeError. Above code can also be written like this.

1raise RuntimeError, "boom"

As per Ruby's documentation if when we do not mention any class while raising an exception then by default it is RuntimeError class.

In Ruby all exceptions are subclasses of Exception class.

Hierarchy of Ruby Exception class

Ruby has lots of built in exceptions. Here is hierarchy of all Ruby's exceptions.

1Exception
2  NoMemoryError
3  ScriptError
4    LoadError
5    NotImplementedError
6    SyntaxError
7  SecurityError
8  SignalException
9    Interrupt
10  StandardError
11    ArgumentError
12      UncaughtThrowError
13    EncodingError
14    FiberError
15    IOError
16      EOFError
17    IndexError
18      KeyError
19      StopIteration
20    LocalJumpError
21    NameError
22      NoMethodError
23    RangeError
24      FloatDomainError
25    RegexpError
26    RuntimeError
27    SystemCallError
28      Errno::*
29    ThreadError
30    TypeError
31    ZeroDivisionError
32  SystemExit
33  SystemStackError

rescue catches that class and all its subclasses

1begin
2  do_something
3rescue NameError
4end

Here we are rescuing all NameError exceptions. However NoMethodError will also be rescued because NoMethodError is a subclass of NameError.

Raising error using class

Following two lines do the same thing.

1raise "boom"
2raise RuntimeError, "boom"

We can raise exceptions of a particular class by stating the name of that exception class.

1raise ArgumentError, "two arguments are needed"
2raise LoadError, "file not found"

Default rescue is StandardError

rescue without any argument is same as rescuing StandardError.

1begin
2rescue
3end

Above statement is same as the one given below.

1begin
2rescue StandardError
3end

Catch multiple types of exceptions in one shot

We can catch multiple types of exceptions in one statement.

1begin
2rescue ArgumentError,NameError
3end

Catching exception in a variable

We can catch exception in a variable like this.

1begin
2rescue StandardError => e
3end

Here e is an exception object. The three main things we like to get from an exception object are "class name", "message" and "backtrace".

Let's print all the three values.

1begin
2   raise "boom"
3rescue StandardError => e
4  puts "Exception class is #{e.class.name}"
5  puts "Exception message is #{e.message}"
6  puts "Exception backtrace is #{ e.backtrace}"
7end

Exceptional Handling in Ruby on Rails using rescue_from

A typical controller could be like this.

1class ArticlesController < ApplicationController
2  def show
3    @article = Article.find(params[:id])
4  rescue ActiveRecord::RecordNotFound
5    render_404
6  end
7
8  def edit
9    @article = Article.find(params[:id])
10  rescue ActiveRecord::RecordNotFound
11    render_404
12  end
13end

We can use rescue_from to catch the exception.

1class ApplicationController < ActionController::Base
2  rescue_from ActiveRecord::RecordNotFound, with: :render_404
3
4  def render_404
5  end
6end
7
8class ArticlesController < ApplicationController
9  def show
10    @article = Article.find(params[:id])
11  end
12
13  def edit
14    @article = Article.find(params[:id])
15  end
16end

Custom exceptions

Sometimes we need custom exceptions. Creating custom exceptions is easy.

1class NotAuthorizedError < StandardError
2end
3
4raise NotAuthorizedError.new("You are not authorized to edit record")

NotAuthorizedError is a regular Ruby class. We can add more attributes to it if we want.

1class NotAuthorizedError < StandardError
2  attr_reader :account_id
3
4  def initialize(message, account_id)
5    #invoke the constructor of parent to set the message
6    super(message)
7
8    @account_id = account_id
9  end
10end
11
12raise NotAuthorizedError.new("Not authorized", 171)

rescue nil

Sometimes we see code like this.

1do_something rescue nil

The above code is equivalent to the following code.

1begin
2  do_something
3rescue
4  nil
5end

Above code is equivalent to the following code.

1begin
2  do_something
3rescue StandardError
4  nil
5end

Do not use Exception as control flow

Let's look at the following code.

1class QuizController < ApplicationController
2  def load_quiz
3    @quiz = current_user.quizzes.find(params[:id])
4    rescue ActiveRecord::RecordNotFound
5      format.json { render status: :not_found, json: { errors: ["Quiz not found"]}}
6    end
7  end
8end

In the above code when quiz id is not found then an exception is raised and then that exception is immediately caught.

Here the code is using exception as a control flow mechanism. What it means is that the code is aware that such an exception could be raised and is prepared to deal with it.

The another way to deal with such a situation would be to not raise the exception in the first place. Here is an alternative version where code will not be raising any exception.

1class QuizController < ApplicationController
2  def load_quiz
3    @quiz = current_user.quizzes.find_by_id(params[:id])
4    unless @quiz
5      format.json { render status: :not_found, json: { errors: ["Quiz not found"]}}
6    end
7  end
8end

In the above case instead of using find code is using find_by_id which would not raise an exception in case the quiz id is not found.

In ruby world we like to say that an exception should be an exceptional case. Exceptional case could be database is down or there is some network error. Exception can happen anytime but in this case code is not using catching an exception as a control flow.

Long time ago in the software engineering world GOTO was used a lot. Later Edsger W. Dijkstra wrote a famous letter Go To Statement Considered Harmful. Today it is a well established that using GOTO is indeed harmful.

Many consider using Exception as a control flow similar to using GOTO since when an exception is raised it breaks all design pattern and exception starts flowing through the stack. The first one to capture the exception gets the control of the software. This is very close to how GOTO works. In Ruby world it is well established practice to not to use Exception as a control flow.