The Pug Automatic

Custom 404 error page with Rails 4

Written August 8, 2014. Tagged Ruby on Rails.

This is what I did to get a custom 404 error page on Rails 4, without replacing the default 500 and 422 error pages.

There are other solutions where you just use the router as the exceptions app, but then you have to handle those other errors as well.

It's very much based on this Gist by Turadg Aleahmad, but with some cleanup and fixes.

Code changes

Remove the default public/404.html to avoid any collisions.

Modify these files like so:

config/application.rb
# …

module NameOfMyApp
class Application < Rails::Application
# …

require Rails.root.join("lib/custom_public_exceptions")
config.exceptions_app = CustomPublicExceptions.new(Rails.public_path)
end
end
config/routes.rb
Rails.application.routes.draw do
match "/404" => "errors#error404", via: [ :get, :post, :patch, :delete ]

# …
end

Add these files:

lib/custom_public_exceptions.rb
class CustomPublicExceptions < ActionDispatch::PublicExceptions
def call(env)
status = env["PATH_INFO"][1..-1]

if status == "404"
Rails.application.routes.call(env)
else
super
end
end
end
app/controllers/errors_controller.rb
class ErrorsController < ApplicationController
def error404
render status: :not_found
end
end
app/views/errors/error404.erb
<p>Sorry! No such page!</p>

Verify in development

To see the page in development, just visit /404.

If you see the default Rails 404 page, you probably forgot to remove public/404.html.

If you want to make sure it actually works, change config/environments/development.rb to say

# Do not commit!
config.consider_all_requests_local = false

instead of true.

Just don't keep that value, since you'll get less helpful errors in development, and you'll also disable the /rails/info/properties page with debug info.

Tests

I haven't been able to figure out a way to do production-style error handling in a single test, so I settled for this:

spec/features/errors_spec.rb
require "rails_helper"

describe "404 page" do
it "is customized" do
# Haven't been able to get the "show instead of exceptions" thing working in tests, but this at least makes sure the page can render correctly.
visit "/404"
expect(page.status_code).to eq 404
expect(page).to have_content("Sorry!")
end
end