ruby

Handling Exceptions in Grape for Ruby

Kingsley Chijioke

Kingsley Chijioke on

Handling Exceptions in Grape for Ruby

Grape is a popular Ruby framework for building RESTful APIs. Exception handling plays a crucial role in ensuring the stability and reliability of any application, including those made with Grape.

This article will explore the basics of Grape exception handling, including customizing exceptions. We'll also touch on some best practices, and how to integrate your app with AppSignal for enhanced error monitoring and management.

Let's get started!

Basics of Grape Exception Handling

In this tutorial, we’ll see how to handle exceptions in a Grape API built in Rails. I have made a demo job board API for this, and you can check out the source code on GitHub.

Raising an Exception

You can raise an exception in Grape by using error!. For example, in the job API mentioned above, we have a show route that returns a job based on the ID. We can return a 404 error when the record is not available, like this:

Ruby
get do if job present job else error!('404 Not Found', 404) end end

When you raise an exception, you’ll want to handle it in a “unique” way — you most likely will not want to send the raised exception to your users.

In Ruby, we have a default mechanism for exception handling. It works by wrapping code that might raise an exception in a begin block. The rescue block is used to handle the exception that has been raised.

Ruby
begin #... process, may raise an exception rescue => #... error handler else #... executes when no error ensure #... always executed end

So, here is what that will look like in a typical scenario:

Ruby
begin File.readlines('input.txt').each { |line| values << Float(line) } rescue Errno::ENOENT p 'file not found' rescue ArgumentError p 'file contains unparsable numbers' else print values end

The rescue_from Method

When you raise exceptions, or they happen without your direct involvement, you’ll want to handle them properly. By default, Grape provides a rescue_from method. This allows you to specify a block of code that gets executed when defined exceptions are raised.

So, to “rescue” or handle the 404 error we raised before any other one that arises in the jobs resource, we can use the rescue_from method. The method is added above the jobs resource.

Ruby
# Rescue 404 errors rescue_from :all do |error| error!({ error: error.message }, 404) end # Jobs resource resource :jobs do desc 'Return list of jobs' get do ... end ... end

We can also specify the content type to be used:

Ruby
rescue_from :all do |error| error!({ error: error.message }, 404, { 'Content-Type' => 'text/error' }) end

This way of handling an exception is too generic — we are rescuing every form of exception and returning an error with a 404 status code. That is misleading if our API users expect to get a 400 status code.

We can instead specify the exception we want to handle:

Ruby
rescue_from ActiveRecord::RecordNotFound do |error| error!({ error: error.message }, 404, { 'Content-Type' => 'text/error' }) end rescue_from :all do |error| error!({ error: error.message }, 500, { 'Content-Type' => 'text/error' }) end

When we encounter an ActiveRecord::RecordNotFound error, we’ll return an error message with a 404 status code. Otherwise, we’ll return an error message with a 500 status code.

This shows that we can improve on what we currently have, but what if we want an error handler that rescues from all errors? That's where customizing exceptions comes in.

Customizing Exceptions in Grape for Ruby

Depending on the type of error encountered, this error handler should be able to return an error message alongside the correct status code.

First, create a file called exceptions_handler. Then, we’ll move our current exception handlers into the file:

Ruby
# frozen_string_literal: true module V1 module ExceptionsHandler extend ActiveSupport::Concern included do rescue_from ActiveRecord::RecordNotFound do |error| error!({ error: error.message }, 404, { 'Content-Type' => 'text/error' }) end rescue_from :all do |error| error!({ error: error.message }, 500, { 'Content-Type' => 'text/error' }) end end end end

Our ExceptionHandler module uses ActiveSupport::Concern , allowing us to access functionalities like included and class_methods. In the snippet above, we have the error handlers in the included block, so wherever this module is included, they will be available as they’re defined.

We can go ahead and remove the error handler from the files where we had them previously. Then we can include the ExceptionsHandler module in our API entry file — api.rb:

Ruby
# frozen_string_literal: true module V1 class API < Grape::API include ExceptionsHandler mount V1::Jobs end end

Let’s create a base error class for our errors. This class will be responsible for returning the error response.

Ruby
module V1 module Exceptions class BaseError < StandardError attr_reader :status, :message def initialize(message: nil, status: nil) @status = status || 500 @message = message || "Something unexpected happened." end def body Rack::Response.new({ error: message }.to_json, status) end end end end

The class accepts two keyword parameters: a message string and a status. If none is passed, we’ll use the default.

In the body method, we return a Rack response. By default, the rescue_from handler must return a Rack::Response object, call error!, or raise an exception.

We can go ahead and make use of it in the ExceptionsHandler:

Ruby
included do rescue_from ActiveRecord::RecordNotFound do |error| error!({ error: error.message }, 404, { 'Content-Type' => 'text/error' }) end rescue_from :all do |error| Exceptions::BaseError.new(message: error.message).body end end

When we call the /error endpoint, we’ll see oops returned as the response. At this point, we can create a class for NotFound errors.

Ruby
module V1 module Exceptions class NotFound < BaseError def initialize(message: nil) super( status: 404, message: message || "Oops, we could not find the record you are looking for." ) end end end end

The NotFound class only accepts message. Since it inherits from BaseError, we need not return a Rack::Response again. We can go ahead and use it in the ExceptionsHandler like this:

Ruby
included do rescue_from ActiveRecord::RecordNotFound do |error| Exceptions::NotFound.new(message: error).body end rescue_from :all do |error| Exceptions::BaseError.new(message: error.message).body end end

Now, if we attempt to raise an error like this manually:

Ruby
raise Exceptions::NotFound.new(message: "Something unexpected happened.......")

This will work fine, but the status code will be 500, because it returns the response in the BaseError class (as the BaseError class handles the error). To fix that, we’ll need to modify the ExceptionHandler to explicitly use the NotFound class to handle the error instead.

So, whenever an error corresponding to ActiveRecord::RecordNotFound and V1::Exceptions::NotFound is encountered, use Exceptions::NotFound. Otherwise, use Exceptions::BaseError.

Ruby
included do rescue_from ActiveRecord::RecordNotFound do |error| Exceptions::NotFound.new(message: error).body end rescue_from V1::Exceptions::NotFound do |error| Exceptions::NotFound.new(message: error.message).body end rescue_from :all do |error| Exceptions::BaseError.new(message: error.message).body end end

You can see that we’ll need specificrescue_fromblocks as we create more error classes. We can improve this by using case statements:

Ruby
module V1 module ExceptionsHandler extend ActiveSupport::Concern included do rescue_from :all do |error| case error.class.name when 'ActiveRecord::RecordNotFound', 'V1::Exceptions::NotFound' Exceptions::NotFound.new(message: error.message).body else Exceptions::BaseError.new(message: error.message).body end end end end end

Et voilà!

Best Practices and Tips

While there are tons of best practices that you can employ for exception handling, here are a few quick tips to follow:

  1. Group related exceptions: As we saw in the code above, grouping related exceptions allow us to have maintainable code. As the number of exceptions we want to handle increases, we can add them to our list.
  2. Use helpers like error! to quickly raise exceptions. This simplifies your exception handling.
  3. Make use of exception monitoring tools like AppSignal.

AppSignal Integration: Grape for Ruby

AppSignal helps you to monitor and track errors in your applications. Integrating AppSignal with your Grape API gives you valuable insights into exceptions. This guide shows you how to integrate AppSignal with your Grape API. Whenever an error occurs in your API, you’ll see it in your AppSignal dashboard, like so:

AppSignal Dashboard

Wrapping Up

Exception handling is a critical aspect of developing robust APIs. In this tutorial, we’ve seen how to properly handle exceptions in a Grape API. We also briefly looked at some best practices and AppSignal's integration for Grape.

Exception handling is an ongoing process — one you’ll need to improve on consistently.

Happy coding!

P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

P.P.S. Did you know that AppSignal offers an Active Record integration? Find out more.

Kingsley Chijioke

Kingsley Chijioke

Our guest author Kingsley is a Software Engineer who enjoys writing technical articles.

All articles by Kingsley Chijioke

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps