The following blog post contains material either currently found or soon to be incorporated into my new book, "Easy Active Record for Rails Developers". Among many other topics, you'll learn about model generation, migrations, validations, associations, scopes, joins, includes, forms integration, nested forms, and model testing with RSpec and FactoryGirl. The book is now available, head over to this website's home page to learn more.


Rails bundles default 404 (file not found), 422 (unprocessable entity), and 500 (internal server error) pages into every newly generated application. While they get the job done, these pages are pretty bland, so in this post I’ll show you how to update them to suit the design of your application. As an example, here’s what the default 404 page looks like:

If you open any Rails application lacking custom error pages, you’ll find this 404 page in the file public/404.html. Therefore the easiest possible solution to creating custom error pages would be to simply edit each of these files as desired. Once updated, you can view them in your development environment by navigating directly to them (error pages aren’t shown by default in Rails’ development environment when an error occurs; more on this later in the post). For instance to see the custom 404 page you’d navigate to http://0.0.0.0:3000/404.html.

While you could get away with directly modifying the 404.html, 422.html, and 500.html pages, an alternative approach exists that provides more flexibility in terms of how these errors are handled and eliminates the need to potentially duplicate any layout markup that is otherwise automatically injected into your views. Even better, this alternative approach is accomplished in a few simple steps. Start by modifying your application.rb file, adding the line config.exceptions_app = self.routes. For instance here’s what the ArcadeNomad config/application.rb file looks like after adding this line:

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

This setting tells Rails to allow any exceptions to be handled by another application, which is in this case the application router.

Next, add the following three lines to the bottom of your config/routes.rb file:

match '/404', to: 'errors#file_not_found', via: :all
match '/422', to: 'errors#unprocessable', via: :all
match '/500', to: 'errors#internal_server_error', via: :all

The match method is used to match a URL to one or more routes. It’s a bit more flexible than for instance the get method because you can configure it to be triggered in conjunction with any matching route and HTTP method as opposed to for instance just a route matching the get method. You can pass specific HTTP methods into the via option, however we want to match routes in conjunction with any HTTP method, and so I’ve passed all into via.

Save these changes and next generate the errors controller and associated actions referenced in these newly created routes:

$ rails g controller Errors file_not_found unprocessable internal_server_error

Restart your Rails server, and navigate to some random nonexistent page. You’ll see that your development server does not respond as desired. This is because you need to tell Rails to no longer dump detailed debugging information to the HTTP response, a feature that is logically desired during the typical debugging process. You can do this by adding the following line to your config/environments/development.rb file:

config.consider_all_requests_local = false

After having added this line, restart your Rails server again, navigate to a non-existent URL, and you should see the file_not_found default view!

With the dynamic error handlers implemented, you have the convenience of allowing the default layout to be wrapped around the views, and can optionally implement more advanced handlers. For instance you can take advantage of various request methods to retrieve parts of the request (the requested page, parameters, etc) and build in logic that suggests possible desirable destinations!

If you’ve built a cool custom handler, tell me about it in the comments!

Comments