It's a little known fact that you can easily substitute the default Rails HTML error pages with something more pleasant. You may have noticed the 404.html
, 422.html
and 500.html
files that are generated with every new Rails project and wondered if there's a clean way to style them like the rest of your application. There is, and it's surprisingly simple.
The default status code templates are served by a Rack exception application. You can override this to be any Rack compatible app, including your applications router:
# config/application.rb
config.exceptions_app = self.routes
This will route any exceptions caught to your router Rack app. Now you'll want to define routes to display those errors yourself:
# config/routes.rb
get "/404", :to => "errors#not_found"
get "/422", :to => "errors#unacceptable"
get "/500", :to => "errors#internal_error"
This will route each error code to it's respective action in ErrorsController
. Now we'll want to define those actions:
class ErrorsController < ApplicationController
def not_found
render :status => 404
end
def unacceptable
render :status => 422
end
def internal_error
render :status => 500
end
end
We tell each action to render the appropriate HTTP status code related to the error that's been caught. All that's left to do now is create the view related to each action and you're done:
# app/views/errors/not_found.html.haml
%h1 404 - Not Found
When we visit /404
our 404 - Not Found
view should render as expected. Now you can style your error pages without having to duplicate any styles into the public directory of your application.
So far we've got working error pages, but it doesn't feel like the most DRY implementation. We could make it more RESTful by refactoring our errors controller to use a show
action instead.
Let's start by changing our routes:
# config/routes.rb
%w( 404 422 500 ).each do |code|
get code, :to => "errors#show", :code => code
end
Now we need to ensure our ErrorsController
uses the code
parameter we're passing through:
class ErrorsController < ApplicationController
def show
render status_code.to_s, :status => status_code
end
protected
def status_code
params[:code] || 500
end
end
To clean things up even more I've created a status_code
method which defaults to a 500
, this will protect any cases where we might not have the code present in the params
hash.
The final alteration as part of this refactor is to rename our view files to use status codes rather than our previous naming scheme:
# app/views/errors/404.html.haml
%h1 404 - Not Found
And that's it. We've now got a reusable errors controller which is flexible enough for us to add new error types to in the future (by adding a new code to the error codes array and a corresponding view file).
It's worth noting that you shouldn't be doing anything fancy in these views. The reason these pages are rendered is because something has most likely gone wrong in your application, so you should probably stray away from making calls to the database or performing more complex actions.