When dealing with third-party or custom exceptions in a Rails controller, your first instinct might be to implement a vanilla Ruby
If you are an experienced Rails developer, you might also be familiar with the rescue_from declaration.
In both cases, rescuing the exception means that you are now responsible for rendering the appropriate error page. And what if your controller responds to both HTML and JSON requests? You’ll need to remember to render an error page tailored for each.
A much easier solution, especially for a typical “404 not found” scenario, is to leverage the built-in numbered error pages. Rails ships with some prefabricated error pages, which are stored in the
public/404.html“The page you were looking for doesn’t exist (404)”
public/422.html“The change you wanted was rejected (422)”
public/500.html“We’re sorry, but something went wrong (500)”
To use them, add your custom exception to
config.action_dispatch.rescue_responses, like this:
Now, whenever an
InvoiceNotFound exception is raised (and not explicitly rescued), Rails will automatically show the
public/404.html error page.
In fact, this is why built-in exceptions like
ActiveRecord::RecordNotFound automatically render an appropriate error page. As explained in the Rails Guides, several exceptions are mapped to HTTP status codes out of the box:
Most Rails apps will need a “You are not allowed to access this page (403)” error page to handle authorization errors. You can use the
rescue_responses mapping and numbered error pages to accomplish this.
For example, when using the pundit gem, authorization errors take the form of
Pundit::NotAuthorizedError. Adding the mapping is straightforward:
config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden
Now create a
public/403.html file and Rails will automatically render it whenever a
Pundit::NotAuthorizedError is encountered. (In practice, I will often just copy and paste an existing error page and tweak the text.)
The great thing about
rescue_responses is that Rails knows to render the error as JSON when the client requests it. That way, an API client will get a machine-readable response instead of the contents of
public/403.html, for example.
If the request contains the
Accept: application/json header, then Rails will render the error response like this:
Whether you’re configuring error handling using
rescue_responses, or building custom responses using
rescue_from, you’ll need a way to test the results. Here are some tips.
By default, Rails will show you stack traces and other troubleshooting tools in development, but not in production. This applies to
rescue_responses as well, which makes it difficult to judge exactly what end-users will see when an error occurs.
To mimic the production settings and ensure that you are seeing your custom error responses exactly as an end-user would, you can temporarily modify this setting:
When testing how error responses will be handled for JSON clients, make sure you configure your API client (e.g. Postman) with the following headers:
rescue_responses mapping is often overlooked. Before writing a custom
rescue_from handler in your Rails controller, consider whether the exception could be handled by a generic HTTP status code and error page. If so, a 1-line addition to
rescue_responses in your
config/application.rb might be all you need.
For more background, check out: