Rails 3.1 - Adding custom 404 and 500 error pages

January 5, 2012


This post was originally published in the Rambling Labs Blog on January 5, 2012.


As I said when announcing the Rambling Labs new site, we’ve been learning a lot of stuff while building it.

Something that we didn’t have the chance to implement on our current projects (but that we will be including soon), is adding custom error pages to the site. So far, what we were looking for was two things: a custom 404 error page and a custom generic 500 error page.

For the experience I have now with Rails, I thought this would be a piece of cake. Well, in fact… it would’ve been if we were using Rails 2. But guess what? The error handling behavior in Rails 3 is not what you would expect. Even worse, it’s broken for routing errors!

For what I could find on StackOverflow (question 1, question 2), in order to handle errors in a rails 3 application, this is what you have to add to the ApplicationController:


UPDATE

The render_error method was missing the layouts, so a funky error happened when using nested templates. The code for ApplicationController has been updated.



UPDATE September 23rd, 2012

Some refactoring made to the ApplicationController, DRYing up. Thanks to Alexey for his suggestion on the comments!


class ApplicationController < ActionController::Base
  # ...

  unless Rails.application.config.consider_all_requests_local
    rescue_from Exception, with: lambda { |exception| render_error 500, exception }
    rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
  end

  private
  def render_error(status, exception)
    respond_to do |format|
      format.html { render template: "errors/error_#{status}", layout: 'layouts/application', status: status }
      format.all { render nothing: true, status: status }
    end
  end

  # ...
end

But, as I said before, this doesn’t work for routing errors. To solve this, first generate the ErrorsController and the error_404 and error_500 views with this:

rails generate controller errors error_404 error_500

Add this to your just generated ErrorsController:

class ErrorsController < ApplicationController
  def error_404
    @not_found_path = params[:not_found]
  end

  def error_500
  end
end

Then, customize the error_404.html.erb and error_500.html.erb views (error_404.html.haml and error_500.html.haml in my case) in the app/views/errors directory. This is what I got for the error_404:

%h2 404
%div
  %h3 We're sorry
  %p
    The content that you requested could not be found.
  %p
    You tried to access '#{@not_found_path}', which is not a valid page.
  %p
    Want to
    %a{href: root_path} go back to our home page
    and try again?

Also, add these lines at the end of your config/routes.rb file:

  unless Rails.application.config.consider_all_requests_local
    match '*not_found', to: 'errors#error_404'
  end

And remove these two routes from the config/routes.rb file, since they are unnecessary:

  get "errors/error_404"
  get "errors/error_500"

One last thing. The errors are not shown on development, so to test them out and make sure that they work as expected, temporarily remove the Rails.application.config.consider_all_requests_local condition from both the config/routes.rb and the app/controllers/application_controller.rb files.

That should do it for now!

Don’t forget to write the tests for your ErrorsController and add the consider_all_requests_local condition again.

Enjoy! :)