Custom Exceptions in Ruby
Tags: ruby, learning, tutorial
Published: 2018-07-03 12:44:38

---
title: Custom Exceptions in Ruby
description: Almost everything in Ruby is an object and errors are no exception.
cover_image: https://blog.appsignal.com/images/blog/2018-07/custom-exceptions.jpg
tags: #ruby, #learning, #tutorial
canonical_url: https://blog.appsignal.com/2018/07/03/custom-exceptions-in-ruby.html
published: true
---

A little while ago we talked about [exceptions in Ruby](https://blog.appsignal.com/2018/03/13/exceptions-in-ruby.html). This time we explore ways of creating custom exceptions specific to your app’s needs.

Let's say we have a method that handles the uploading of images while only allowing JPEG images that are between 100 Kilobytes and 10 Megabytes. To enforce these rules we raise an exception every time an image violates them.

```ruby
class ImageHandler
  def self.handle_upload(image)
    raise "Image is too big" if image.size > 10.megabytes
    raise "Image is too small" if image.size < 100.kilobytes
    raise "Image is not a JPEG" unless %w[JPG JPEG].include?(image.extension)

    #… do stuff
  end
end
```

Every time a user uploads an image that doesn't meet the rules, our (Rails) web app displays the default Rails 502 error page for the uncaught error.

```ruby
class ImageUploadController < ApplicationController
  def upload
    @image = params[:image]
    ImageHandler.handle_upload(@image)

    redirect_to :index, :notice => "Image upload success!"
  end
end
```

The Rails generic error page doesn't offer the user much help, so let's see if we can improve on these errors. We have two goals: inform the user when the file size is outside the set bounds and prevent hackers from uploading potentially malicious (non-JPEG) files, by returning a `403` forbidden status code.

## Custom error types

Almost everything in Ruby is an object, and errors are no exception. This means that we can subclass from any error class and create our own. We can use these custom error types in our `handle_upload` method for different validations.

```ruby
class ImageHandler
  # Domain specific errors
  class ImageExtensionError < StandardError; end
  class ImageTooBigError < StandardError
    def message
      "Image is too big"
    end
  end
  class ImageTooSmallError < StandardError
    def message
      "Image is too small"
    end
  end

  def self.handle_upload(image)
    raise ImageTooBigError if image.size > 10.megabytes
    raise ImageTooSmallError if image.size < 100.kilobytes
    raise ImageExtensionError unless %w[JPG JPEG].include?(image.extension)

    #… do stuff
  end
end
```

First, we've added three new classes to the handler that extend from `StandardError`. For the image size errors, we've overridden the `message` method of `StandardError` with an error message we can show to users. The way `raise` was called in the `handle_upload` method has also changed, by replacing the custom `StandardError` message with a different error type we can raise a different, more specific, error.

Now, we can use these custom error types in our controller to return different responses to errors. For instance, we can return the specific error message or a specific response code.

```ruby
class ImageUploadController < ApplicationController
  def upload
    @image = params[:image]
    ImageHandler.handle_upload(@image)

    redirect_to :index, :notice => "Image upload success!"

  rescue ImageHandler::ImageTooBigError, ImageHandler::ImageTooSmallError => e
    render "edit", :alert => "Error: #{e.message}"

  rescue ImageHandler::ImageExtensionError
    head :forbidden
  end
end
```

This is already a lot better than using the standard `raise` calls. With a little bit more subclassing we can make it make it easier to use, by rescuing entire error groups rather than every error type separately.

```ruby
class ImageHandler
  class ImageExtensionError < StandardError; end
  class ImageDimensionError < StandardError; end
  class ImageTooBigError < ImageDimensionError
    def message
      "Image is too big"
    end
  end
  class ImageTooSmallError < ImageDimensionError
    def message
      "Image is too small"
    end
  end

  def self.handle_upload(image)
    raise ImageTooBigError if image.size > 10.megabytes
    raise ImageTooSmallError if image.size < 100.kilobytes
    raise ImageExtensionError unless %w(JPG JPEG).include?(image.extension)

    #… do stuff
  end
end
```

Instead of rescuing every separate image dimension exception, we can now rescue the parent class `ImageDimensionError`. This will rescue both our `ImageTooBigError` and  `ImageTooSmallError`.

```ruby
class ImageUploadController < ApplicationController
  def upload
    @image = params[:image]
    ImageHandler.handle_upload(@image)

    redirect_to :index, :notice => "Image upload success!"

  rescue ImageHandler::ImageDimensionError => e
    render "edit", :alert => "Error: #{e.message}"

  rescue ImageHandler::ImageExtensionError
    head :forbidden
  end
end
```

The most common case for using your own error classes is when you write a gem. The [mongo-ruby-driver](https://github.com/mongodb/mongo-ruby-driver/blob/master/lib/mongo/error.rb) gem is a good example of the use of custom errors. Each operation that could result in an exception has its own exception class, making it easier to handle specific use cases and generate clear exception messages and classes.

Another advantage of using custom exception classes is that when using exception monitoring tools like AppSignal. These tools give you a better idea as to where exceptions occurred, as well as grouping similar errors in the user interface.

If you liked this article, check out more of what we wrote on [AppSignal Academy](https://blog.appsignal.com/category/academy.html?utm_source=DevTo&utm_medium=DevToBlogPost&utm_campaign=DevToContentPosting_CustomExceptions). AppSignal is all about building better apps. In our Academy series, we'll explore application stability and performance, and explain core programming concepts.

We'd love to know what you thought of this article, or if you have any questions. We're always on the lookout for topics to investigate and explain, so if there's anything magical in Ruby you'd like to read about, don't hesitate to leave a comment.