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:
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.
So, here is what that will look like in a typical scenario:
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.
We can also specify the content type to be used:
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:
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:
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:
Let’s create a base error class for our errors. This class will be responsible for returning the error response.
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
:
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.
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:
Now, if we attempt to raise an error like this manually:
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
.
You can see that we’ll need specificrescue_from
blocks as we create more error classes. We can improve this by using case statements:
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:
- 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.
- Use helpers like
error!
to quickly raise exceptions. This simplifies your exception handling. - 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:
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.