Raise error rails

It's not enough to raise exceptions in Ruby. You need to raise exceptions effectively. Here's how to create a human-readable—and diagnosable—exception object.

Raising Exceptions in Ruby

Rescuing pre-defined exceptions is one thing, but to more effectively utilize exceptions within your application, it is important to understand how to create and raise your own. At its core, every Ruby exception stems from a built-in Exception class, and includes a handful of built-in methods, but the most commonly used exception method is message. This method can be used to retrieve a specific exception message from a raised exception object.

Raising Generic Exceptions

Like the title of this section implies, raising exceptions in Ruby can be done using the raise method. While a blank exception can be raised, however, most exceptions include a message with additional information about the error. For example, a default RuntimeError exception can be raised with a custom error message in just one line:

raise "This is an exception"

While this is valuable for raising generic exceptions, it is also possible to raise an instance of an Exception class, such as StandardError:

raise StandardError.new "This is an exception"

Keep in mind, however, that while StandardError exceptions are intended to be rescued, Exception exceptions are not (see Exception Handling in Ruby). This means that if you choose to raise a new Exception instance, you will not be able to rescue it without explicitly rescuing every top-level Exception instance, which could cause unexpected problems down the line.

raise Exception.new "This is an exception"

Raising Custom Exceptions

In a nutshell, every custom Ruby exception should extend StandardError, rather than the Exception class (the reason for this is outlined in Exception Handling in Ruby). With this in mind, the simplest custom exception class that we can define will look something like this:

class MyCustomException < StandardError
end

Given the above class definition, we will be able to raise a MyCustomException exception, yet have access to all the bells and whistles that come from the built-in StandardError class. But what if you wanted to define a default error message, or pass additional data along with your custom error? To do this, we simply need to override the constructor to accept custom parameters:

class MyCustomException < StandardError
  def initialize(msg="This is a custom exception", exception_type="custom")
    @exception_type = exception_type
    super(msg)
  end
end

This class definition allows us to raise our custom exception with the additional data:

begin
    raise MyCustomException.new "Message, message, message", "Yup"
rescue MyCustomException => e
    puts e.message *# Message, message, message*
    puts e.exception_type *# Yup*
end

Writing to STDERR

Outside of raising exceptions, Ruby also supports writing directly to STDERR. Where puts writes messages directly to STDOUT, STDERR.puts can be used to write raw messages to STDERR without interrupting program execution. This is useful when you need to track debug data or keep track of caught and handled exceptions.

STDERR.puts "I am an error message"

Handling exceptions in your API applications is quite an important thing, and if you want to keep things DRY, you should think how to do it in the proper way. In our Ruby on Rails API course, I’ve shown how to implement the error handling using ErrorSerializer and ActiveModelSerializers gem and here I’m going to show you even better approach to this topic when you can unify EVERY error across the whole API application.

Rails api thumbnail

Ruby On Rails REST API
The complete guide

Create professional API applications that you can hook anything into! Learn how to code like professionals using Test Driven Development!

Take this course!

UPDATE: I’ve recently came with even greater way of handling Errors in Rails Web applications using «dry-monads»! It still uses this approah to serilize the errors for JSON:API purposes, but the actual mapping can be done in the more neat way!

The final approach

There is no point to cover the whole thought process of how we came with the final result, but if you’re interested in any particular part just say it in the comments. The basic assumptions were to keep things DRY and unified across the whole application.

So here is the code.

The standard error.
    # app/lib/errors/standard_error.rb

    module Errors
      class StandardError < ::StandardError
        def initialize(title: nil, detail: nil, status: nil, source: {})
          @title = title || "Something went wrong"
          @detail = detail || "We encountered unexpected error, but our developers had been already notified about it"
          @status = status || 500
          @source = source.deep_stringify_keys
        end

        def to_h
          {
            status: status,
            title: title,
            detail: detail,
            source: source
          }
        end

        def serializable_hash
          to_h
        end

        def to_s
          to_h.to_s
        end

        attr_reader :title, :detail, :status, :source
      end
    end

First of all we needed to have the Base error, which will be a fallback for any exception risen by our application. As we use JSON API in every server’s response, we wanted to always return an error in the format that JSON API describes.

We extracted all error-specific parts for every HTML status code we wanted to support, having a fallback to 500.

More detailed errors

As you can see this basic error was just a scaffold we could use to override particular attributes of the error object. Having that implemented, we were able to instantiate several case-specific errors to deliver more descriptive messages to our clients.

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end

All errors are very clean and small, without any unnecessary logic involved. That’s reasonable as we don’t want to give them an opportunity to fail in an unexpected way, right?

Anyway defining the error objects is only the half of the job.

Serializing the error in Ruby Application

This approach above allowed us to use something like:

    ...
    def show
      Article.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      e = Errors::NotFound.new
      render json: ErrorSerializer.new(e), status: e.status
    end
    ...

To serialize the standard responses we use fast_jsonapi gem from Netflix. It’s quite nice for the usual approach, but for error handling not so much so we decided to write our own ErrorSerializer.

    # app/serializers/error_serializer.rb

    class ErrorSerializer
      def initialize(error)
        @error = error
      end

      def to_h
        serializable_hash
      end

      def to_json(payload)
        to_h.to_json
      end

      private

      def serializable_hash
        {
          errors: Array.wrap(error.serializable_hash).flatten
        }
      end

      attr_reader :error
    end

The logic is simple. It accepts an object with status, title, detail and source methods, and creates the serialized responses in the format of:

    # json response

    {
      "errors": [
        {
          "status": 401,
          "title": "Unauthorized",
          "detail": "You need to login to authorize this request.",
          "source": {
            "pointer": "/request/headers/authorization"
          }
        }
      ]
    }

The only problem here is that handling all of those errors in every action of the system will end up with a lot of code duplications which is not very DRY, is it? I could just raise proper errors in the services, but standard errors, like ActiveRecord::RecordNotFound would be tricky. This is then what we ended up within our API ApplicationController:

    # app/controllers/application_controller.rb

    class ApplicationController < ActionController::API
      ...
      include Api::ErrorHandler
      ...
    end

We just included the ErrorHandler module, where we implemented all mappings and the logic responsible for all error handling.

    module Api::ErrorHandler
      extend ActiveSupport::Concern

      ERRORS = {
        'ActiveRecord::RecordNotFound' => 'Errors::NotFound',
        'Driggl::Authenticator::AuthorizationError' => 'Errors::Unauthorized',
        'Pundit::NotAuthorizedError' => 'Errors::Forbidden'
      }

      included do
        rescue_from(StandardError, with: lambda { |e| handle_error(e) })
      end

      private

      def handle_error(e)
        mapped = map_error(e)
        # notify about unexpected_error unless mapped
        mapped ||= Errors::StandardError.new
        render_error(mapped)
      end

      def map_error(e)
        error_klass = e.class.name
        return e if ERRORS.values.include?(error_klass)
        ERRORS[error_klass]&.constantize&.new
      end

      def render_error(error)
        render json: Api::V1::ErrorSerializer.new([error]), status: error.status
      end
    end

At the top, we added a nice mapper for all errors we expect to happen somewhere. Then we rescue from the default error for the rescue block, which is the StandardError, and call the handle_error method with the risen object.

Inside of this method we just do the mapping of the risen error to what we have server responses prepared to. If none of them matches, we fall back to our Errors::StandardError object so client always gets the nice error message in the server response.

We can also add extra notifiers for any error that is not mapped in the handler module, so application admins will be able to track the unexpected results.

Rising errors in the application

In Driggl we managed to create a unified solution for the whole error handling across our API application. This way we can raise our errors in a clean way without repeating any rescue blocks, and our ApplicationController will always handle that properly.

    def show
      Article.find!(params[:id])
    end

or

    def authorize!
      raise Errors::Unauthorized unless currentuser
    end

Handling validation errors

Well, that is a nice solution, but there is one thing we intentionally omitted so far and it is: validation failure.

The problem with validations is that we can’t write the error object for invalid request just as we did for the rest, because:

  • the failure message differs based on the object type and based on attributes that are invalid
  • one JSON response can have multiple errors in the returned array.

This requires us add one more error, named Invalid, which is an extended version of what we had before.

    # app/lib/errors/invalid.rb

    module Errors
      class Invalid < Errors::StandardError
        def initialize(errors: {})
          @errors = errors
          @status = 422
          @title = "Unprocessable Entity"
        end

        def serializable_hash
          errors.reduce([]) do |r, (att, msg)|
            r << {
              status: status,
              title: title,
              detail: msg,
              source: { pointer: "/data/attributes/#{att}" }
            }
          end
        end

        private

        attr_reader :errors
      end
    end

You can see that the main difference here is the serialized_hash and initialize method. The initialize method allows us to pass error messages hash into our error object, so then we can properly serialize the error for every single attribute and corresponding message.

Our ErrorSerializer should handle that out of the box, returning:

    # json response

    {
      "errors": [
        {
          "status": 422,
          "title": "Unprocessable entity",
          "detail": "Can't be blank",
          "source": {
            "pointer": "/data/attributes/title"
          }
        },
        {
          "status": 422,
          "title": "Unprocessable entity",
          "detail": "Can't be blank",
          "source": {
            "pointer": "/data/attributes/content"
          }
        }
      ]
    }

The last thing, however, is to rise it somewhere, so the handler will get the exact error data to proceed.

In the architecture we have, it’s a not big deal. It would be annoying if we would go with updating and creating objects like this:

    app/controllers/articles_controller.rb

    class ArticlesController < ApplicationController
      def create
        article = Article.new(article_params)
        article.save!
      end

      private

      def article_attributes
        params.permit(:title)
      end
    end 

As this would force us to rescue the ActiveRecord::RecordInvalid error in every action, and instantiate our custom error object there like this:

      def create
        article = Article.new(article_params)
        article.save!
      rescue ActiveRecord::RecordInvalid
        raise Errors::Invalid.new(article.errors.to_h)
      end

Which again would end up with repeating a lot of rescue blocks across the application.

In Driggl however, we do take advantage of Trailblazer architecture, with contracts and operations, which allows us to easily unify every controller action in the system.

    # app/controllers/articles_controller.rb

    class ArticlesController < ApplicationController
     def create
        process_operation!(Admin::Article::Operation::Create)
      end

      def update
        process_operation!(Admin::Article::Operation::Update)
      end
    end

I won’t go into details of Trailbalzer in this article, but the point is that we could handle the validation errors once inside of the process_operation! method definition and everything works like a charm across the whole app, keeping things still nice and DRY

    # app/controllers/application_controller.rb

    class ApplicationController < ActionController::API
      private

      def process_operation!(klass)
        result = klass.(serialized_params)
        return render_success if result.success?
        raise Errors::Invalid.new(result['contract.default'].errors.to_h)
      end

      def serialized_params
        data = params[:data].merge(id: params[:id])
        data.reverse_merge(id: data[:id])
      end

      def render_success
        render json: serializer.new(result['model']), status: success_http_status
      end

      def success_http_status
        return 201 if params[:action] == 'create'
        return 204 if params[:action] == 'destroy'
        return 200
      end
    end

Summary

You could think it’s a lot of code, but really, for big applications it’s just nothing comparing to repeating it in hundred of controllers and other files. In this form we managed to unify all our errors across the whole API application and we don’t need to worry anymore about unexpected failures delivering to the client.

I hope this will be useful for you too, and if you’ll find any improvements for this approach, don’t hesitate to let me know in the comments!

Special Thanks:
  • Cristopher Jeschke for a nice cover image

Other resources: 

  • Custom exceptions in Ruby by Appsignal
  • Error Hierarchy in Ruby by Starr Horne

Creating custom exceptions specific to your Ruby on Rails app’s needs is a powerful way to convey intent. What do they
look like and where should you put them?

Contents

  • Introducing raise "This is bad"
  • The Ruby Exception class
  • Where to put your custom exception
  • Writing your custom exception

Introducing raise "This is bad"

In one of the projects I was brought into, I saw a lot of raise "Error". By a lot, I mean more than 100+. This would
result in control flows that looked like this:

begin
  do_something_bad
rescue => e 
  if e.message == "this"
    handle_this_in_this_way(e)
  else
    handle_this(e)
  end
end

Almost as bad:

value = do_something rescue nil  

Scary, right?

Our error reporter will treat all of these the same. This turns out to be a disaster when you have more than
a few developers working on exceptions.

But this isn’t about writing predictable code. This is about using custom exceptions to convey intent.

The Ruby Exception class

Descendants of Exception are used to communicate between Kernel#raise and rescue statements in begin ... end blocks.
Exception objects hold information about the exception — like its type, and optionally a description or trace.

Applications should subclass StandardError or RuntimeError to provide custom classes and add additional information.

When an exception has been raised but not yet handled from rescue, ensure, at_exit or END blocks, the global $!
will contain the current exception and $@ will contain the current exception’s backtrace.

It’s recommended that an application should have a single subclass of StandardError or RuntimeError and have specific
exception types inherit from it. This allows the user to rescue a generic exception to catch all exceptions the application may raise,
even if future versions of the application add new exception subclasses.

class MyApp
  class Error < StandardError; end 
  class WidgetError < Error; end 
  class ThingError < Error; end 
end

To handle both WidgetError and ThingError, the user can rescue MyApp::Error.

Where to put your custom exception

Before we write a custom exception, we need to understand how Ruby on Rails is going to find it. Because Ruby on Rails
has autoload magic, we can’t just assume that the error can come from anywhere.

This leaves us with a few options:
1. Use require in our Ruby on Rails application to load them
2. Use Ruby on Rails conventions to place errors in a file it can find
3. Write the errors in the class

Option 1 means we start to work against Ruby on Rails conventions, which is bad.

Option 2 is nice, but it can get quickly out of hand if you decide to have lots of different types of errors.

Option 3 is practical, doesn’t introduce a lot of cognitive load, and can fit nicely in all sorts of code.

Combining 2 and 3 might be the most ideal solution: this way we can have DRY exceptions if we need them, but specific
errors as we require them.

lib/my_app.rb is what Discourse uses. Solidus follows a similar pattern.

module MyApp
  class MyAppError < StandardError
    def initialize(msg = nil, object: nil, options: {})
      super(msg)
      @object = object 
      @options = options
    end 
  end 
  class GatewayError < MyAppError; end 
end

Writing your custom exception

Now that we’ve defined our generic error object MyAppError, we can start using it in our classes.

class UploadHandler
  MAX_FILE_SIZE = 100.megabytes

  class UploadSizeError < MyAppError 
    def message
      "File is too big. File should be 1mb, file is #{@obj.file_size}."
    end
  end
  
  def initialize(upload)
    @upload = upload 
  end 
  
  def process
    validate_size!
    # do something
  end  

  private 

  def validate_size!
    unless @upload.file.size <= MAX_FILE_SIZE
      raise UploadSizeError.new('', object: @upload)
    end 
  end
end 

That way, in our controller:

def create 
  @upload = UploadHandler.new(upload_params)
  begin 
    @upload.process        
  rescue MyAppError => e 
    flash[:alert] = e.message
    return render :new 
  end 
  redirect_to uploads_path, notice: "Success!"
end 

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:


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.

For example, consider a api_exceptions.rb file where we are rescuing from all
the exceptions like so:

1module ApiExceptions
2  rescue_from StandardError, with: :handle_api_exception
3  rescue_from ActiveRecord::RecordNotUnique, with: :handle_record_not_unique_exception
4
5  def handle_api_exception(exception)
6    case exception
7    # handles the exceptions
8  end
9
10  def handle_record_not_unique_exception(exception)
11    # handles record not unique exception
12  end
13end

The RecordNotUnique exception is a child class of StandardError. When we
rescue from StandardError all the child exception classes will also be caught.
So the rescue_from ActiveRecord::RecordNotUnique in the above example is
redundant. The code will never reach this line because the RecordNotUnique
exception will already be caught by the rescue_from StandardError statement.

The correct way of rescuing the errors will be, like so:

1module ApiExceptions
2  rescue_from StandardError, with: :handle_api_exception
3
4  def handle_api_exception(exception)
5    case exception
6
7    when ActiveRecord::RecordNotFound
8      handle_record_not_unique_exception(exception)
9
10    # handles the other exceptions
11  end
12
13  def handle_record_not_unique_exception(exception)
14    # handles record not unique exception
15  end
16end

In the above example, we are rescuing StandardError and all its children
classes with handle_api_exception method. And inside handle_api_exception
method case is used to handle
the required child exception classes specifically.

Certain exceptions and when they are raised

  • Pundit::NotAuthorizedError is raised when any authorization error from
    Pundit gem raises and we
    can handle it with 403 or forbidden status.

  • ActionController::ParameterMissing is raised when a required parameter is
    missing. Consider a case when we have strong params defined for a post
    request and those params are missing in the request header. We can handle this
    via 500 or internal_server_error status.

  • ActiveRecord::RecordNotFound is raised when Rails does not find any
    record. When we use find method to search for a record with the provided
    id in params and the id is mistaken or the record is missing from DB,
    Rails raises ActiveRecord::RecordNotFound exception. We can handle this
    with a 404 or not_found status.

  • ActiveRecord::RecordInvalid is raised on failure of validations declared in
    model for any record creation or updation. Let’s say we have some email
    validation declared in model and the input email does not match with the given
    Regex pattern Rails raises ActiveRecord::RecordInvalid exception. We can
    handle this with a 422 or unprocessable_entity status.

  • ActiveRecord::RecordNotUnique is raised when a record cannot be inserted or
    updated because it would violate a uniqueness constraint from DB. We can
    handle this with unprocessable_entity or 422 status.

  • PG::NotNullViolation: ERROR: null value in column «name» of relation «users» violates not-null constraint
    for missing mandatory input. We should handle the database errors starting
    with PG:: or SQLite3:: with internal_server_error or 500 status. If we
    keep this as unprocessable_entity then it won’t raise Honeybadger issues,
    meaning it will log the error silently. These errors are very rare and we
    should be getting notified of these errors by Honeybadger in Github.

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:


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:


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.

Rescuing from specific exception

Ruby’s Exception is the parent class to all errors. So one might be tempted to
always rescue from this exception class and get the «job» done. But DON’T!

Exception includes the class of errors that can occur outside your
application. Things like memory errors, or SignalException::Interrupt(sent
when you manually quit your application by hitting Control-C), etc. These are
the errors that you don’t want to catch in your application as they are
generally serious and related to external factors. Rescuing the Exception
class can cause very unexpected behaviour.

StandardError is the parent of most Ruby and Rails errors. If you catch
StandardError you’re not introducing the problems of rescuing Exception, but
it is not a great idea either. Rescuing all application-level errors might cover
up unrelated bugs that you don’t know about.

The safest approach is to rescue the error(or errors) you are expecting and deal
with the consequences of that error inside the rescue block.

In the event of an unexpected error in your application you want to know that a
new error has occurred and deal with the consequences of that new error inside
its own rescue block.

Being specific with rescue means your code doesn’t accidentally swallow new
errors. You avoid subtle hidden errors that lead to unexpected behaviour for
your users and bug hunting for you.

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.

Using bang methods in controller actions

But, just like everything in software engineering, the suggestion we had made in
last section also has some exceptions. Like in controllers etc, we should be
trying our level best to keep controllers as skinny as possible. So adding
repetitive unless or if statements as a replacement for rescue statements
won’t scale. Thus in such cases, what we should do is use the bang(!) versions
of ActiveRecord methods, like create!, update!, destroy! or save! within
the controller actions. This would raise an exception in case there’s a failure.

But where to handle theses exceptions? Well, in the chapter where we had cleaned
up the application controller, we had added a concern named ApiExceptions.
This concern is included in the ApplicationController. Which means all other
controllers will be having access to the methods defined in that concern. In
that concern we have several rescue_from statements, which handles specific
exceptions.

Thus in our controller, we could write something like so, and both the success
and failure cases will be handled:

1class QuizController < ApplicationController
2  before_action :load_quiz!, only: %i[update]
3
4  def update
5    @quiz.update!(quiz_params) # control wouldn't even reach here if exception was raised in load_quiz!
6    respond_with_success(t("successfully_created", entity: "Quiz")) # control will only reach here if the above statement didn't raise any exception
7  end
8
9  private
10
11    def load_quiz!
12      @quiz = current_user.quizzes.find_by!(id: params[:id])
13    end
14end

Notice how we have named the method as load_quiz! with a bang(!) rather than
simply load_quiz? It’s to denote that the particular method has the potential
to raise an exception. If no record is found by the find_by! method then a
ActiveRecord::RecordNotFound exception will be raised.

There is a problem in the above code block, which has to deal with code
conventions that we follow in BigBinary. That’s, if we are querying using the
id attribute only then we should use the find method over find_by! because
in the find method we can directly pass the id value without defining any
key and it makes the code cleaner. If for a given id no record is found by
find method then the same exception will be raised that is
ActiveRecord::RecordNotFound.

So, in the above code block the load_quiz! method needs to be updated like so:

1def load_quiz!
2  @quiz = current_user.quizzes.find(id)
3end

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

Retrying errors in a fashionable way

Some processes are very error-prone. A good example is requesting the data from the API or any other external source. A timeout error might be raised, but it does not mean that we can’t pull the resource that we are requesting. Sometimes all we need is to give a second chance or even a third. We can achieve it with the retry keyword:

require 'net/http'

retry_count = 0
uri = URI("http://somewebsite.com")

begin
  response = Net::HTTP.get_response(uri)
  # do something with response
rescue Net::ReadTimeout => e
  retry_count += 1
  retry if retry_count < 3

  raise(e)
end

It will request the response three times if Net::ReadTimeout error will be raised. How about making the code more reusable? Let’s create a wrapper for retrying specified errors, specified number of times:

module Retryable
  def try_with_retry(error:, retries:, &block)
    retry_count = 0

    begin
      block.call
    rescue error => e
      retry_count += 1
      retry if retry_count < retries

      raise(e)
    end
  end
end

After including the Retryable module, we can attempt to perform any action and retry given number of times when given error will be raised:

try_with_retry(error: Net::ReadTimeout, retries: 3) do
  response = Net::HTTP.get_response(uri)
  # do something with response
end

You can also modify the try_with_retry method to accept multiple errors, which will make the solution even more flexible.

Good practices

While raising and rescuing errors may seem to be a straightforward thing to do, as in other code aspects, some good practices are worth using, so our code is readable and easily extendable.

Raise or fail?

Since raise and fail does the same thing, they shouldn’t be used alternately. There is one golden rule to distinct which one should be used:

  • Use raise if you are catching an error thrown by an external gem or library to re-raise the error
  • Use fail if you want to raise the error in your code and let the program know that you failed to do something

Build your error hierarchy

As you may know, in the case of errors’ classes, there is a hierarchy. The top class is Exception from which other classes like ScriptError or StandardError inherits. In turn, the StandardError is the parent class for ArgumentErrorNameError, etc.

When building the validation process, you may end up with the following code that tries to handle all errors and report to the user:

begin
  do_something
rescue RequiredInputError, UniqInputError => e
  # respond to the user
end

With own error hierarchy the process of rescuing errors is more natural and readable:

class ValidationError < StandardError; end
class RequiredInputError < ValidationError; end
class UniqInputError < ValidationError; end

begin
  do_something
rescue ValidationError => e
  # respond to the user
end

With the above approach, you still have access to e.class, which will return either RequiredInputError or UniqInputError.

Clean up your Rails controllers

If in your Rails controller you rescue from the same error in many actions, you may end up with the code that is hard to maintain and is far from being stuck to the DRY rule:

class ApiController < ApplicationController
  def index
    posts = Post.all
    render json: { posts: posts, success: true }
  rescue PermissionError => e
    render json: { success: false }
  end

  def show
    post = Post.find(params[:id])
    render json: { post: post, success: true }
  rescue PermissionError => e
    render json: { success: false }
  end
end

The rescue_from method is coming to the rescue:

class ApiController < ApplicationController
  rescue_from PermissionError, with: :render_error_response

  def index
    posts = Post.all
    render json: { posts: posts, success: true }
  end

  def show
    post = Post.find(params[:id])
    render json: { post: post, success: true }
  end

  private

  def render_error_response
    { success: false }
  end
end

Do not rescue everything

Avoid code where you rescue from Exception, which is the top-level error class. Such code would be very error-prone, and you will handle any error that will be raised. Instead of this, try to handle as few errors as possible and let the other errors be shown to the world.

Respect the conventions

In many cases, you can meet two versions of one method: some_method and some_method!. In Rails, the banger method indicates that the error will be raised when something would go wrong, and the outcome of the action will be different from the expected one. While in Ruby, the method with banger indicates that the method will alter the object that invoked it:

name = "John Doe"
name.gsub("Doe", "Smith")
# => "John Smith"
name
# => "John Doe"

name.gsub!("Doe", "Smith")
# => "John Smith"
name
# => "John Smith"

Raise or not to raise?

Not every context needs the error to be raised in case something is missed. There are three cases in a typical application or a library when we have to deal with missing things, but each case has its context.

When you request a resource, but it’s not available

For example when you call .find method on the ActiveRecord model:

User.find(1)
# => raise ActiveRecord::RecordNotFound

When you try to find an element in the collection

For example, when you have an array of users, and you want to find the one with John Doe name:

User = Struct.new(:name)

users = [User.new("Tim Doe"), User.new("Kelly Doe")]
users.find { |user| user.name == "John Doe" }
# => nil

When you try to find a node in the website’s source

For example when you are parsing website source with the Nokogiri gem:

Nokogiri::HTML.parse(nil)
# => #<Nokogiri::HTML::Document:0x13600 name="document" children=[#<Nokogiri::XML::DTD:0x135ec name="html">]>

Nokogiri::HTML.parse(nil).css("a")
# => []

When you query the database

When you search for the record, it’s not present, but you can use other criteria as well:

User.where(name: 'John Doe')
# => []

User.where(name: 'John Doe').where(age: 20)
# => []

Context is the king

Don’t stick to one approach and instead consider the context before writing the implementation. If you request a record and it’s not present in the database, should we raise an error? Yes, we should. If you try to find the element in an array and it’s not present, should we raise an error? No, nil is enough.

Sometimes you can keep going even if there are no results. The best example is the Nokogiri case or ActiveRecord – both libraries allow you to chain methods in the call even if one of them is not returning the result you are looking for.

Design pattern to the rescue

There is a null object design pattern that can help you to deal with errors in some cases. You have a User model that can have many addresses assigned, and one of them is current because it has the Address#current column set to true. User can also have no addresses assigned, and in such cases, we would like to render accurate information.

The structure of models in our case looks like the following:

class User < ActiveRecord::Base
  has_many :addresses

  def current_address
    addresses.current
  end
end

class Address
  belongs_to :user

  def current
    find_by(current: true)
  end
end

We can now display the current address full_address value or render accurate information when the address is not found:

user = User.find(...)

if user.current_address.present?
  user.current_address.full_address
else
  'no address'
end

let’s refactor it a little bit and use one-liner:

user = User.find(...)
user.current_address&.full_address.presence || 'no address'

Here comes the null object pattern to the rescue:

class EmptyAddress
  def full_address
    'no address'
  end
end

As you can see, our null object is just a simple Ruby object that implements only methods we need to call. We have to modify the User#current_address a little bit:

class User < ActiveRecord::Base
  has_many :addresses

  def current_address
    addresses.current || EmptyAddress.new
  end
end

and our final code is really clean now:

user = User.find(...)
user.current_address.full_address

Monads to the rescue

According to the documentation of the dry-monads gem:

Monads provide an elegant way of handling errors, exceptions, and chaining functions so that the code is much more understandable and has all the error handling, without all the ifs and elses.

Let’s give it a try be refactoring our previous code:

require 'dry/monads/all'

class User < ActiveRecord::Base
  include Dry::Monads
  
  has_many :addresses

  def current_address
    addresses.current
  end 
end

user = User.find(...)
current_address = Maybe(user.current_address)
current_address.value_or('no address')

Since the dry-monads gem and the dry-rb family deserves a separate article, I won’t explore this topic more. If you are interested in further information, visit the GitHub page 

Rescuing from multiple errors in an elegant way

Sometimes we have to take into account many different errors and support all of them. It often happens when we create a request class for the external API, and it can throw a bunch of various errors like timeouts or permission ones.

Usually, the code may looks like the below one:

class Request
  def call
    perform_action
  rescue SomeAPI::Error => e
    if e.message.match(/account expired/)
      send_email_with_notification_about_expired_account
    elsif e.message.match(/api key invalid/)
      send_email_with_notification_about_invalid_api_key
    elsif e.message.match(/unauthorized action performed/)
      # log some useful info
    else
      raise(e)
    end
  end
end

It does not look good. It has many if and is not readable at all. One of the methods of refactoring such blocks of code is to overwrite the standard StandardError class and create separated error classes for each case:

class SomeAPIAccountExpired < StandardError
  def self.===(exception)
    exception.class == SomeAPI::Error && exception.message.match(/account expired/)
  end
end

class SomeAPIInvalidAPIKey < StandardError
  def self.===(exception)
    exception.class == SomeAPI::Error && exception.message.match(/api key invalid/)
  end
end

class SomeAPIUnauthorizedAction < StandardError
  def self.===(exception)
    exception.class == SomeAPI::Error && exception.message.match(/unauthorized action performed/)
  end
end

you can now handle it gently:

class Request
  def call
    perform_action
  rescue SomeAPI::Error => e
    handle_api_error(e)
  end

  private

  def handle_api_error(error)
    case error
    when SomeAPIAccountExpired
      send_email_with_notification_about_expired_account
    when SomeAPIInvalidAPIKey
      send_email_with_notification_about_invalid_api_key
    when SomeAPIUnauthorizedAction
      # log some useful info
    else
      raise(error)
    end
  end
end

Such a structure allows you to catch multiple errors from the same source but with a different message that is very readable and meaningful.

Having fun with altering the default Ruby behavior

Since raise is just a method defined on the Kernel, you can overwrite it and do something creative with it:

module MyOwnRaise
  def raise(*args)
    # do something amazing
    super
  end
end

class Object
  include MyOwnRaise
end

raise ArgumentError # let's have some fun

For example, you can create the code to collect and count the errors and then present the summary in a friendly dashboard inside your app.

Improve Article

Save Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    An exception is an unwanted or unexpected event, which occurs during the execution of a program i.e at runtime, that disrupts the normal flow of the program’s instructions. As we know, the code enclosed between begin and end block is totally secured for handling Exceptions and the rescue block tells the ruby, the type of exception is to be handled. 
    Mainly Runtime Exceptions are handled if they are any error in the code, maybe written by a naive coder, these types of errors may rise “divided by zero error”, “index out of the range error”, etc where the program stops execution whenever these exceptions occur if not handled. By using, raise statement we can raise manual user-defined exceptions. Like, for example, in an ATM transaction program to raise an exception when the user enters an invalid account number to withdraw.
    Syntax:

    raise exception-type "exception message" condition

    Whenever the raise statement is raised it calls the rescue and execution starts from there. raise statement by default raises RuntimeError. 
    Example : 
     

    Ruby

    begin

        puts 'This is Before Exception Arise!'

           raise 'Exception Created!'

        puts 'After Exception'

    end

    Output : 
     

    This is Before Exception Arise!
    Exception Created!
    • Check out the four types of raise statements in the below code: 
      Example : 

    Ruby

    puts "type-1n"

    begin

       raise

    rescue

       puts 'Tony got rescued.'

    end

    puts 'Tony returned safely'

    puts "ntype-2n"

    begin

       raise 'Quill got rescued.'

       puts 'quill' 

    rescue StandardError => e 

       puts e.message

    end

    puts 'Quill is back to ship.'

    puts "ntype-3n"

    begin

       raise StandardError.new 'Groot got rescued.'

    rescue StandardError => e 

      puts e.message

    end

    puts "ntype-4n"

    begin

    a = 30

    b = 0

        raise  ZeroDivisionError.new 'b should not be 0' if b == 0

        puts a/b

    rescue StandardError => e 

       puts e.message 

    end

    puts

    begin

    a = 30

    b = 2

        raise  ZeroDivisionError.new 'b should not be 0' if b == 0

        print "a/b = ", a / b

    rescue StandardError => e 

       puts e.message 

    end

    Output:

    type-1
    Tony got rescued.
    Tony returned safely
    
    type-2
    Quill got rescued.
    Quill is back to ship.
    
    type-3
    Groot got rescued.
    
    type-4
    b should not be 0
    
    a/b = 15
    • Check out the difference between the Runtime and Raised Exception. 
      Example : 

    Ruby

    puts "Raised Exception:n"

    begin

    a = 30

    b = 0

        raise ZeroDivisionError.new 'b should not be 0' if b == 0

        print "a/b = ", (1 + 2) * (a / b)

    rescue ZeroDivisionError => e 

       puts e.message

       puts e.backtrace

    end

    puts "nRuntime Exception:n"

    begin

    a = 30

    b = 0

    x=(1 + 2) * (a / b)

        raise ZeroDivisionError.new 'b should not be 0' if b == 0

        print "a/b = ", (1 + 2) * (a / b)

    rescue ZeroDivisionError => e 

       puts e.message

       puts e.backtrace

    end

    Output:

    Raised Exception:
    b should not be 0
    (repl):8:in `'
    /run_dir/repl.rb:41:in `eval'
    /run_dir/repl.rb:41:in `run'
    
    
    Runtime Exception:
    divided by 0
    (repl):21:in `/'
    (repl):21:in `'
    /run_dir/repl.rb:41:in `eval'
    /run_dir/repl.rb:41:in `run'
    • In above example, Runtime Exception has an error stack

    Ruby’s raise syntax gives you a couple of options for specifying the kind of error you want raised. In the code below, I’ve shown three ways to raise a RuntimeError.

    raise "hello"
    raise RuntimeError, "hello"
    raise RuntimeError.new("hello")
    
    # ...all of the above result in "RuntimeError: hello"
    

    That’s nice, but what happens when I want to raise something other than an exception? What if I wanted to raise a number? Well, Ruby won’t let me. I’d get an error message like this:

    raise 1
    # TypeError: exception class/object expected
    

    Now this message might lead you to believe that raise expects an exception class/object as a parameter. But that’s incorrect!

    Introducing the exception method

    If you do raise foo the raise method doesn’t expect foo to be an exception object. It expects that it will get an exception object whenever it calls foo.exception.

    The thing to remember is that you can pass ANYTHING to raise, just as long as it has a method called exception that returns an exception.

    So, if you wanted to, you could monkeypatch ruby’s number classes to allow you to raise a number. Here’s what that might look like:

    class Fixnum
      def exception
        RuntimeError.new("I'm number: #{ self }")
      end
    end
    
    raise 42
    # ...results in raise_number.rb:7:in `<main>': I'm number: 42 (RuntimeError)
    

    This is a neat party trick, but could it ever be useful in real life? The main practical application I see for this technique is to separate the logic required to build an exception from the logic that decides to raise the exception. This is certainly a bit of an edge case. But let’s see what that might look like.

    A possibly practical example

    Suppose I want to read a line of data from some kind of IO. It could be network IO, it could be a file. It doesn’t really matter. I just want to read the data and see if it’s valid.

    If the data I read isn’t valid, I want to raise an exception. But the exception needs to be tailored to the input. A network connection needs to have different debug info than a local file. I can do that by providing custom exception methods for each kind of input class. Here’s some pseudo-ruby showing what that might look like.

    # These three classes represent different kinds of IO with different exceptions.
    class NetworkConnection
      ...
      def exception
        NetworkConnectionError.new(url: url, ...)
      end
    end
    
    class LocalFile
      ...
      def exception
        FileError.new(path: path, ...)
      end
    end
    
    class UnixPipe
      ...
      def exception
        PipeError.new(...)
      end
    end
    
    def read_all(*items)
      items.each do |item|
        if item.readline != "foo"
          # We raise the item, which causes the appropriate exception class to be used. 
          raise item
        end
      end
    end
    
    read_all(NetworkConnection.new(url: "example.com"), LocalFile.new("/something"), UnixPipe.new)
    
    

    What to do next:

    1. Sign up for a FREE Honeybadger account

      Honeybadger helps you find and fix errors before your users
      can even report them. Get set up in minutes and check
      monitoring off your to-do list.

      Try Honeybadger for FREE

    2. Get the Honeybadger newsletter

      Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.

    author photo

    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she’s not neck-deep in other people’s bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    • @starrhorne
      Author Twitter

    More articles by Starr Horne

    Понравилась статья? Поделить с друзьями:
  • Raise error powershell
  • Raise error postgres
  • Raise error file does not start with riff id
  • Raise error could not locate runnable browser webbrowser error could not locate runnable browser
  • Raise application error sqlerrm