2

I'm currently generating dynamic error pages for 500 and 404 errors. I want to expand this to 422 errors. Here's what we have so far.

config/application.rb

config.exceptions_app = self.routes

controllers/errors_controller.rb

class ErrorsController < ApplicationController
  def not_found
    render status: 404
  end

  def internal_server_error
    render status: 500
  end

  def unacceptable
    render status: 422
  end
end

routes.rb

get '/404' => 'errors#not_found'
get '/500' => 'errors#internal_server_error'
get '/422' => 'errors#unacceptable'

The public/422.html page has been deleted. The error view pages have been created but omitted for brevity. When a 404 or 500 error is raised, the error pages are shown. However, when I receive a 422 error I get the following error page.

enter image description here

I've seen numerous tutorials implementing this same approach and it works. However, I'm receiving a generated Rails error and not the error page I created. What's wrong and how do I fix this?

Tutorials I've looked at:

thank_you
  • 11,001
  • 19
  • 101
  • 185
  • Have you tried [`rescue_from`](http://api.rubyonrails.org/v5.0/classes/ActiveSupport/Rescuable/ClassMethods.html)? – tadman Jan 10 '17 at 20:52
  • None of the tutorials I've found use `rescue_from`. I'll post links to the tutorials I looked at in my question. – thank_you Jan 10 '17 at 20:53
  • That's why I linked to it. The Rails documentation covers this in more depth than most tutorials do. – tadman Jan 10 '17 at 21:10
  • Yes, I've used `rescue_from` and it works flawlessly. I was hoping I didn't have to use `rescue_from` in the first place. No other tutorials mention this approach and if I can get rid of `rescue_from` and the method it calls then I would have simplified my code. – thank_you Jan 10 '17 at 21:26
  • Why would you hope that? It's the tool for the job. It's the simplest way of expressing what you want to do. The default is to just fail out. – tadman Jan 10 '17 at 21:29
  • I had another developer comment that it might be easier for `config.exceptions_app = self.routes` to do it's job and simply redirect the user to the 422 error page. I researched and found these articles. None of them are using `rescue_from` which made me think there must be another way to accomplish this. But from what you're saying the only way to such a thing is through `rescue_from`. – thank_you Jan 10 '17 at 21:32
  • Unless you have a really compelling argument, I think the `rescue_from` way is the least surprising, least messy way of doing this. Maybe you can add an answer here that explains your solution and your hesitation for going that route so others might understand better. – tadman Jan 10 '17 at 21:34
  • I honestly don't have a compelling argument. For whatever reason these tutorials are incorrect or I'm missing something obvious. `rescue_from` is an appropriate answer. I'll add the answer in two days when I have the ability to do so. – thank_you Jan 10 '17 at 21:36
  • They might be out of date. `rescue_from` wasn't always as well advertised as it should be. – tadman Jan 10 '17 at 21:37
  • @tadman, Thanks for the help. – thank_you Jan 10 '17 at 21:39

1 Answers1

5

I'm another developer who worked on this with @jason328. It turned out to be a multi-part problem, first with general 422 errors, and then with the specific scenario in which Rails was raising ActiveRecord::InvalidAuthenticityToken and not rendering the appropriate page.

1. General 422 Errors

The Rails error page

We got rid of this temporarily in our local development environment by setting config.consider_all_requests_local = false. But then instead of getting our custom error page, we got a blank white page.

The blank white page

As per this Stack Overflow question, we needed match '/422', to: 'errors#unprocessable_entity', via: :all for the route instead of get '/422' => 'errors#unprocessable_entity'.

At this point, generic 422 errors performed as they should. We set up a controller action that raised ActiveRecord::InvalidAuthenticityToken as soon as you hit it, and it rendered our custom 422 page. So for anyone just having trouble with 422 errors in general, the above should cover you.

2. InvalidAuthenticityToken

But since a common cause of 422 errors is actually getting an InvalidAuthenticityToken error in the wild, it seems worth describing the rest of the problem we were seeing. In the actual scenario where the app was generating its own InvalidAuthenticityToken error, we were now getting a text-only 500 error, instead of our custom 422 page.

Text-only 500 error

We were able to trace this to the FAILSAFE_RESPONSE in ActionDispatch::ShowExceptions#render_exception. This is where Rails takes the exception that's been thrown and converts it to a [status, body, headers] response array. If another exception gets thrown during that time, rather than getting caught in an endless loop, it gives up and returns the FAILSAFE_RESPONSE. In this case, another InvalidAuthenticityToken error was getting thrown while putting together the response.

At this point, it was time for the :rescue_from strategy:

rescue_from ActionController::InvalidAuthenticityToken,
            with: :rescue_invalid_authenticity_token

def rescue_invalid_authenticity_token
  #...notify services as if this error weren't being rescued

  redirect_to '/422'
end

with a redirect to keep us safe from any more InvalidAuthenticityToken errors in the same request.

Community
  • 1
  • 1
arthurlewis
  • 412
  • 5
  • 11