Rambling Labs Blog Ramblings on software development

  • Rails 3.1 - Adding custom 404 and 500 error pages

    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! :)

  • blog comments powered by Disqus