Wednesday, 1 April 2015

Custom Error Pages with Rails 4

Here is how we can integrate custom error pages instead of  default Rails error pages, for 400(Not Found) and other Server Errors.

Following is the example code snippet :

First step is to remove the Rails default error pages from applications "public" folder.

Application Config
#config/application.rb
config.exceptions_app = self.routes

For Production make sure you have following setting in your environment file:

#config/production.rb
config.consider_all_requests_local = false

Also, if you want to test the changes on your local server, do the same setting in your development env file :

#config/development.rb
config.consider_all_requests_local = false

Routes
#config/routes.rb
if Rails.env.production?
   get '404', :to => 'application#page_not_found'
   get '422', :to => 'application#server_error'
   get '500', :to => 'application#server_error'
end

Errors Controller
#controllers/errors_controller.rb
def page_not_found
  respond_to do |format|
    format.html { render template: 'errors/not_found_error', layout: 'layouts/application', status: 404 }
    format.all  { render nothing: true, status: 404 }
  end
end

def server_error
  respond_to do |format|
    format.html { render template: 'errors/internal_server_error', layout: 'layouts/error', status: 500 }
    format.all  { render nothing: true, status: 500}
  end
end

Errors Layout (totally static -- for server errors only)

#views/layouts/error.html.erb
<!DOCTYPE html>
<html>
<head>
  <title><%= action_name.titleize %> :: <%= site_name %></title>
  <%= csrf_meta_tags %>
  <style>
    body {
        background: #fff;
        font-family: Helvetica, Arial, Sans-Serif;
        font-size: 14px;
    }
    .error_container {
        display: block;
        margin: auto;
        margin: 10% auto 0 auto;
        width: 40%;
    }
    .error_container .error {
        display: block; 
        text-align: center;
    }
    .error_container .error img {
        display: block;
        margin: 0 auto 25px auto;
    }
    .error_container .message strong {
        font-weight: bold;
        color: #f00;
    }
    .error_container .contact_info {
        display: block;
        text-align: center;
        margin: 25px 0 0 0;
    }
    .error_container .contact_info a {
        display: inline-block;
        margin: 0 5px;
        opacity: 0.4;
        transition: opacity 0.15s ease;
    }
    .error_container .contact_info a:hover {
        opacity: 0.8;
    }
  </style>
</head>
<body>
  <div class="error_container">
      <%= yield %>
  </div>
</body>
</html>

Error Views

#views/errors/not_found_error.html.erb    
<div class="error">
    <h2>Sorry, this page has moved, or doesn't exist!</h2>
</div>

#views/errors/internal_server_error.html.erb
<div class="error">
    <%= image_tag('layouts/error/alert.png', :width => "150") %>
    <div class="message">
        <strong>Error!</strong>
        We're sorry, but our server is experiencing problems :(
    </div>
    <div class="contact_info">
        <%= link_to image_tag('layouts/error/contact/twitter.png'), "", :title => "Follow Updates On Twitter", :target => "_blank" %>
        <%= link_to image_tag('layouts/error/contact/facebook.png'), "http://www.facebook.com/", :title => "See Progress On Facebook", :target => "_blank" %>
        <%= link_to image_tag('layouts/error/contact/fusion.png'), "", :title => "Catch Our Fusion Feed Updates", :target => "_blank"  %>
        <%= link_to image_tag('layouts/error/contact/instagram.png'), "", :title => "See Updates On Instagram", :target => "_blank"  %>
        <%= link_to image_tag('layouts/error/contact/youtube.png'), "", :title => "See Updates On YouTube", :target => "_blank"  %>
    </div>
</div>

And thats it.... ;)

Go and test it by raising some error in your application like: routing errors etc.

5 comments:

  1. Very nicely Explained..

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Thanks for the article, helped!

    Errors happen not only on get methods. So in routes.rb I added:

    unless Rails.application.config.consider_all_requests_local
    match '404', via: :all, to: 'errors#error_404'
    match '422', via: :all, to: 'errors#error_422'
    match '500', via: :all, to: 'errors#error_500'
    end


    If user directly go to '/500', server must output 404 error.
    In errors_controler.rb:

    class ErrorsController < ApplicationController
    protect_from_forgery except: :all

    def error_404
    respond_to do |format|
    format.html { render action: 'error_404', status: 404 }
    format.all { render status: 404, nothing: true }
    end
    end

    def error_422
    error_xxx 422
    end

    def error_500
    error_xxx 500
    end

    private

    def error_xxx(number)
    if request.path == "/#{number}"
    error_404
    else
    respond_to do |format|
    format.html { render status: number }
    format.all { render status: number, nothing: true }
    end
    end
    end

    end

    ReplyDelete
  4. Nice article, very helpful. In order for your requests to be routed properly on development, you need to do something like :
    unless Rails.env.test?
    get '404', :to => 'application#page_not_found'
    get '422', :to => 'application#server_error'
    get '500', :to => 'application#server_error'
    end

    on your routes.rb.
    Thank you again,
    JC

    ReplyDelete
  5. @Sergei Dubovsky, Thanks for your suggestions. I will update my post.

    ReplyDelete