Search
⌘K
    to navigateEnterto select Escto close

    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

    The rescue method catches a 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

    Catching 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

    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

    The above code can also be written like so, since by default StandardError is raised:

    1begin
    2  do_something
    3rescue StandardError
    4  nil
    5end

    Exception handling in Ruby on Rails using rescue_from

    A typical controller could look 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.

    The rescue_from directive is an exception handler that rescues the specified exceptions raised within controller actions and reacts to those exceptions with a defined method.

    For example, the following controller rescues ActiveRecord::RecordNotFound exceptions and passes them to the render_404 method:

    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

    The advantage to rescue_from is that it abstracts the exception handling away from individual controller actions, and instead makes exception handling a requirement of the controller.

    The rescue_from directive not only makes exception handling within controllers more readable, but also more regimented.

    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: { error: "Quiz not found"}}
    6  end
    7end

    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: { error: "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.

    There is nothing to commit in this chapter since all we had done was learning the basics of exception handling in Ruby.

    Previous
    Next