Rescue Me: Techniques for Active Record Error Handling in Ruby on Rails
Tags: rails, ruby, api, tutorial
Published: 2023-01-05 20:15:11

Active Record provides many mighty tools to streamline the work of building a backend API, including for when things go wrong. In this post, we'll walk through three error-handling techniques, getting more sophisticated and powerful as we go.

For the sake of simplicity, our examples will deal with failing to find a record in our database, which we'll illustrate in the context of a standard, RESTful `show` controller action. However, these techniques are applicable to other types of errors as well.

###Basic Error-Handling with Conditional Logic
The simplest way to handle Active Record errors is just to use conditional logic in your controller actions to specify what your API should do if an error arises. We might write our `show` action like this:

```Ruby
def show
  dingus = Dingus.find_by(id: params[:id])
  if dingus?
    render json: dingus, status: :ok
  else
    render json: {error: "Dingus not found"}, status: :not_found
  end
end
```
Let's break down what's happening here. First, we initialize a variable to refer to the `Dingus` record we want from the database, and we use `find_by` to retrieve it with the `id` provided by the params hash. (Read more about the params hash [here](https://dev.to/souzamartin/whose-params-are-they-anyway-using-the-params-hash-in-rails-12ke).) Then, the conditional statement `if dingus?` checks whether that variable has a truthy or falsey value. This works because if the `find_by` operation locates a record and assigns it to the variable, `dingus` is truthy. If no record is found, the value of `dingus` is `nil`, which is falsey. Accordingly, if the condition is met, the API sends the data in its response, along with the successful `:ok` status code. Otherwise, it sends an error message, which we've produced manually as a simple hash, along with the appropriate `:not_found` status.

Handling errors like this has certain advantages. For one thing, this approach is easy to understand, and all the code for it exists in one place. It gives you very specific control over how an individual controller action responds to a certain error, and for that reason this technique can sometimes be useful even if you're using other more sophisticated methods elsewhere—particularly if you're creating custom controller actions.

However, for common errors, this technique is inefficient and clunky. You don't want to have to write separate `if`/`else` logic everywhere, and you definitely shouldn't compose your own error messages for every single case.

###Basic Error-Handling with Rescue Blocks
Fortunately, Active Record gives us more tools and a better way to do things. In our first example, we used a simple logical condition to check whether or not our `show` action found a record, but we can take advantage of some built-in functionality instead.

Certain Active Record methods return instances of special classes called **exceptions** in the event of failure. For instance, the `find` method takes an `id` and returns a `RecordNotFound` exception if there's no record with a matching `id` attribute. It is less flexible than the `find_by` method in our first example, which can search using whatever attribute we like, but returning an exception instead of just `nil` when it doesn't find a matching record is a major advantage. For one thing, exceptions come with their own error messages, which we can access and send to the frontend rather than having to compose them ourselves. More importantly, they also enable us to use **rescue blocks** to define how we want to handle errors.

The `rescue` keyword is kind of like special conditional logic that looks out for exceptions of the specified type. When the right kind of exception occurs, the code block runs, doing whatever we've written to deal with the error.

Let's rewrite our `show` action to use a rescue block:

```Ruby
def show
  render json: Dingus.find(params[:id]), status: :ok
rescue ActiveRecord::RecordNotFound
  render json: {error: "Dingus not found"}, status: :not_found
end
```
Now we're using `find` to retrieve the requested record, and simply sending it to the frontend without assigning it to a variable first (though you can certainly still do so, and might have reason to in certain cases). If no record exists with a matching `id`, `find` returns an `ActiveRecord::RecordNotFound` exception—and when that happens, our rescue block kicks in and runs the same code we used in the `else` part of our first example.

This is a step in the right direction, but we still have to write a rescue block for each controller action. Maybe that's fine if we want to deal with a certain kind of exception for a single action, but it's annoying if we want to handle the same kinds of errors in multiple places.

###Intermediate Error-Handling with `rescue_from`
Fortunately, we can entirely separate our error-handling from our controller actions. Just like `rescue`, `rescue_from` defines a response to a specific type of exception. However, rather than being attached to a particular action, a `rescue_from` is independently defined, and applies any time the specified exception occurs, no matter what action it comes from.

With a `rescue_from`, our whole example controller now looks like this:

```Ruby
class DingusesController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found

  def show
    render json: Dingus.find(params[:id]), status: :ok
  end

  private
  def render_not_found
    render json: {error: "Dingus not found"}, status: :not_found
  end
end
```
Now the `show` action doesn't need to include anything concerned with potential errors. We've refactored the error response into a separate method called `render_not_found`, which our `rescue_from` will use whenever a `RecordNotFound` exception occurs.

At this point, since our error-handling is separated from our controller actions, we can make it even more generally performant by relocating it to our top-level controller, like so:

```Ruby
class ApplicationController < ActionController::API
  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found

  private
  def render_not_found(exception)
    render json: {exception.model: "Not found"}, status: :not_found
  end
end
```
Since `DingusesController` inherits from `ApplicationController`, it will use the `rescue_from` defined there—as will all our other controllers. We've also adjusted the private `render_not_found` method in order to generalize and reuse our error response whenever a `RecordNotFound` exception occurs anywhere. Now it takes the exception instance as a parameter so we can call `exception.model` to get whatever kind of resource wasn't found. In our example case, this would give us `{Dingus: "Not found"}`.

###TLDR
The best approach for handling most Active Record errors in to use `rescue_from` in your top-level application controller. It's a bit more abstract, but it's the most effective and efficient way to cover common types of errors for multiple database resources. Once you understand how to use `rescue_from`, there's probably little reason to ever use an individual rescue block.

However, there may be still be times where good old `if`/`else` logic can provide a good one-off solution to deal with errors in special cases.