Do you know how (static) error pages like 404 or 500 get rendered in a (production) Rails application?
It’s all managed by this Rack Middleware.
When called, this middleware renders an error page. By default if an HTML response is expected it will render static error pages from the
/public
directory. For example when this middleware receives a 500 response it will render the template found in/public/500.html
.
An instance of this middleware is associated to the following Rails configuration:
config.exceptions_app
sets the exceptions application invoked by the ShowException middleware when an exception happens.
Source: http://guides.rubyonrails.org/configuring.html
I want to render a dynamic error page with my application layout.
Let’s start by overwriting exceptions_app
configuration:
1
2
3
4
5
6
7
8
9
# config/application.rb
# ...
module MyApp
class Application < Rails::Application
# ...
config.exceptions_app = routes
# ...
end
end
Doing so, you can now define custom routes:
1
2
3
4
5
6
7
#config/routes.rb
Rails.application.routes.draw do
%w(404 422 500).each do |status_code|
get status_code, to: "errors#show", status_code: status_code
end
# ...
end
You have to create ErrorsController
:
1
2
3
4
5
6
7
8
9
10
11
12
# app/controllers/errors_controller.rb
class ErrorsController < ApplicationController
def show
render status_code.to_s, status: status_code
end
private
def status_code
params[:status_code]
end
end
and a view file for each error code defined in routes.rb. This is a skeleton for 404.html.erb
:
1
2
<h1>Error#not_found</h1>
<p>Find me in app/views/error/not_found.html.erb</p>
Now you can delete the static view files public/404.html
, public/422.html
and public/500.html
.
You are almost ready, but if you want to test this solution in development
, you have to temporarily set config.consider_all_requests_local = false
in config/environments/development.rb
. Otherwise you are going to see the default Rails development error page instead of your custom view.
Gotchas
Will my error notification library still work?
We, at weLaika, are using Airbrake with Errbit and yes, it works. No extra work!
Why do I get a 500 error page for ActiveRecord::RecordNotFound
instead of a 404 error page?
The 404 custom page you created before will be rendered only if Rails can’t find a match in config/routes.rb
. Further more, since ActiveRecord::RecordNotFound
is an exception, Rails does the right thing when it renders the 500 error page.
To fix this, you can use rescue_from
as follows:
1
2
3
4
5
6
7
8
9
10
11
12
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found
protected
def handle_record_not_found
render "errors/404", status: :not_found
end
# ...
end
Be careful if you have multiple rescue_from
conditions, because their order matters!