Step 1: Configure your router as the exceptions app

Since Rails 3 you’ve been able to configure an app to handle exceptions, which you want to point right at your router. To do this, add the following to config/application.rb:

module MyApp
  class Application < Rails::Application
    config.exceptions_app = self.routes
  end
end

Step 2: Add a catch-all route

Make sure this is the last rule in config/routes.rb:

MyApp::Application.routes.draw do
  get "*any", via: :all, to: "errors#not_found"
end

With this, any requested path — whatever the request type — that doesn’t match the previous routing rules will match this rule. The *any path starts with the * wildcard, so it will match anything. The any part is arbitrary, but you have to put something after the * to make it work. I’m sure there’s a good reason why, but somebody else will have to explain it.

Step 3: Implement ErrorsController#not_found

class ErrorsController < ApplicationController
  def not_found
    respond_to do |format|
      format.html { render status: 404 }
    end
  rescue ActionController::UnknownFormat
    render status: 404, text: "nope"
  end
end

You may be wondering why I suggest rescuing ActionController::UnknownFormat instead of adding a format.any block to handle any non-html request types. The problem with format.any is that it will only handle known mime types. This is a-okay for 404’ing .pngs, .json, .xml, etc., but it doesn’t handle the real crazy stuff, like wp-login.php. In other words, when it comes to catch-alls, ActionController::UnknownFormat has a bigger glove than format.any.

Step 4: Add a view

Create app/views/errors/not_found.html.erb and put your 404 page’s markup in there. This will use the application layout by default, so your 404 page will fit in with the rest of your site’s style.

Step 5: Have a Moscow Mule

Or an iced tea. Or some lemonade. Your call.