Using rescue_from to Centralize Controller Errors

rails rescue_from

Tired of repeating the same begin/rescue block in every action? Use rails rescue_from to register one handler per exception class in a controller, so errors are handled in one place. Declare it once and subclasses inherit it.

This guide belongs to the wider topic of ruby on rails debugging.

Item Value
Handler inheritance Handlers are inherited by subclasses
Handler lookup order Right to left, bottom to top, up the hierarchy
Handler forms Method name, Proc, or block
Exceptions raised inside a handler Not propagated up
rescue_with_handler result The exception if handled, nil if not
show_detailed_exceptions? default false

These figures come from the Rails edge API for ActiveSupport::Rescuable::ClassMethods and the rdoc.info page for ActionController::Rescue, which documents Rails 8.0.2. Documentation changes between releases, so check the source before you rely on them.

What rails rescue_from does in a controller

Without a central handler, every risky action grows its own begin/rescue block. The result is repeated code and error responses that drift apart. The rescue_from class method fixes that by mapping an exception class to a handler.

The rdoc.info page for ActionController::Rescue says this module provides rescue_from to controllers, wraps actions to handle configured errors, and configures when detailed exceptions must be shown. In other words, Rails already wraps each action for you. You only decide which errors go where.

Centralizing gives you a few concrete wins:

  • Actions read like the happy path, with no error plumbing mixed in.
  • The same error produces the same response everywhere.
  • Changing how an error is reported means editing one method.

For the wider picture of how errors move through a Rails app, read about rails exception handling.

Declaring handlers: methods, blocks and class names

According to the Rails edge API, rescue_from receives a series of exception classes or class names. It also needs a handler, given either through the with: option or as a block.

That gives you three ways to write a handler:

  1. Pass a method name: rescue_from User::NotAuthorized, with: :deny_access.
  2. Pass a Proc to the with: option.
  3. Give a block that receives the exception: rescue_from “MyApp::BaseError” do |exception| … end.

If you pass neither a with: option nor a block, Rails raises an ArgumentError that begins “Need a handler.” The same method raises an ArgumentError if you pass something that is not an exception class or a string naming one.

The documented example lives in ApplicationController. The deny_access method calls head :forbidden. The show_record_errors method redirects back, or to the root URL, with an alert built from the record’s full error messages. The block for MyApp::BaseError redirects to the root URL with the exception message as the alert.

Handlers that take one argument are called with the exception, so you can inspect it. A handler with no argument still runs, which is enough when the exception type alone tells you what to do.

How Rails picks the handler when several match

Handlers are inherited. The docs state they are searched from right to left, from bottom to top, and up the hierarchy. The handler of the first class for which exception.is_a?(klass) holds true is the one invoked.

The source code explains the odd direction. A comment in the method says it puts the new handler at the end because the list is read in reverse. So the last declaration is checked first.

That leads to a practical rule for ordering your declarations:

  • Declare broad handlers first, such as a base error class for your app.
  • Declare narrow handlers below them, such as a specific subclass.
  • Remember that a subclass matches a handler for its parent, because the test is is_a?.

If you reverse that order, the broad handler sits at the bottom and catches everything before the narrow one is ever consulted. Reading the controller top to bottom, the most specific rule should come last.

Inheritance adds a second dimension. A handler declared in ApplicationController is available in every controller below it, and a subclass can add its own handlers for errors that only matter there.

rails rescue_from Desk setup
A laptop sits beside a leafy plant and a small orange ornament.

Writing handler methods that behave well

A handler is a small piece of code that runs at the worst possible moment, in the middle of a failure. Keep it boring and predictable.

The Rails docs warn that exceptions raised inside exception handlers are not propagated up. That is a good reason to keep handler code short and to test each handler directly, since a bug inside one will not surface the way a normal error does.

A few habits help:

  • Produce exactly one response: render, redirect, or send a bare head response.
  • Make handlers private methods, as the documented example does.
  • Treat the exception message with care before showing it. The docs example puts the message in an alert, which is fine for an error class you wrote yourself but risky for third-party errors whose text you do not control.
  • Name the handler after what it does for the user, such as deny_access, not after the exception.

The handler is also the right place to record the failure. Logging or reporting the exception there means you keep the diagnostic detail even when the user only sees a short message.

Handling unmatched errors and detailed exception pages

Not every error needs a handler. The docs for rescue_with_handler say it matches an exception to a handler by class. If nothing matches, it checks for a handler matching the optional exception.cause.

If neither the exception nor its cause matches, the method returns nil, so you can deal with unhandled exceptions. The docs add that you should re-raise unhandled exceptions if that is what you expect. It returns the exception when it was handled and nil when it was not.

Detailed error pages are a separate setting. The ActionController::Rescue module has a show_detailed_exceptions? method that is only called when consider_all_requests_local is false. It returns false by default, though you may override it, for example to return request.local? so local requests in production still show the detailed page.

What visitors see once a handler or the default behavior runs is a design question of its own, covered in the guide to rails custom error pages.

Frequently asked questions

Can I pass a class name as a string?

Show answer

Yes. The Rails edge API says rescue_from receives exception classes or class names, and its own example uses the string “MyApp::BaseError” with a block.

Does a handler for a parent class catch its subclasses?

Show answer

Yes. The handler of the first class for which exception.is_a?(klass) holds true is the one invoked, so a subclass of the declared class matches.

What happens when two handlers match the same error?

Show answer

The one declared later wins, because handlers are searched from right to left and from bottom to top. Put broad handlers above narrow ones.

Do older Rails versions work the same way?

Show answer

The lookup order is documented the same way in the Rails 5.2 API. Check the docs for your own version before relying on finer details.

Where should I declare the handlers?

Show answer

Declare shared ones in ApplicationController, as the documented example does. Handlers are inherited, so every controller below it gets them.