Cleaning up a Rails routes file

07 Oct 2014

Large Rails apps have a way of gathering cruft. One popular cruft hideout is config/routes.rb. It doesn't change very often, and when it does change it's usually a person getting in there, adding a new resource or a new action, and getting out. So from time to time it's worth checking the routes file and tidying things up.

Probably the most obvious cleanup is removing unused routes. A route which has no corresponding action won't produce a warning, so you have to ferret these out yourself. Take a look at the routes file and see if anything obvious jumps out at you; if you know that a bunch of admin code was just extracted into another app you can look around for routes in an admin namespace. Do a git whatchanged -p app/controllers/ config/routes.rb (or as Brian Ewins suggests, git log -p --no-merges app/controllers/ config/routes.rb because whatchanged is deprecated) and see if recent controller or action removals included removal of orphaned routes. Grab the traceroute gem and see what it finds. This will lead to a smaller file, faster load time, and generally less confusion.

Added 5/5/2015 Another great tool is newrelic_route_check by Dan Mayer. This compares your existing routes to the historical usage from NewRelic's transaction stats and reports routes that haven't been hit recently. You still need to check the routes to see if it's something that only gets hit once a month or something, but it gives some actual numbers and a place to start.

There are some minor cleanups that are worth a look. Sometimes I'll see resources with empty blocks:

resources :products, :only => [:index, :show] do

This is usually leftover from an earlier cleanup; just delete those empty blocks. Same goes for empty member or collection blocks. This is trivial, but it makes the file easier to read.

The default resource declaration - e.g., resources :products - results in routes to seven actions: new, create, edit, update, index, show, and destroy. Occasionally you'll see a resource declaration with all seven actions declared explicitly in an only constraint; in that case you can remove the entire constraint. So this:

resources :products, :only => [:create, :new, :edit, :update, :index, :show, :destroy]

can be boiled down to:

resources :products

The same applies to resource declarations, of course, but with six actions instead of seven.

A variant on this is to replace only blocks with except blocks when those would be shorter. That is, you can replace this:

resources :products, :only => [:create, :new, :edit, :update, :index, :show]

with this:

resources :products, :except => [:destroy]

Some people prefer wrapping single actions in an array when declaring resource restrictions; that is, they prefer resources :products, :only => [:create] to resources :products, :only => :create. I tend to use the bare symbol, but I can see where those folks are coming from - makes it easier to add other actions - so whatever works for your team.

This one is not like the others in that it makes your routes file bigger. But it reduces overall complexity, so I think it belongs here anyway. If you have an action that is serving only to redirect requests to another URL, you can replace that action with a route. So you can replace:

class ProductsController < ApplicationController
  def show
    redirect_to root_path

With this route:

match '/products/:id' => redirect('/')

This trades five lines of code and a file for a single route, which seems like a win to me. There are times when this isn't quite as clearcut, like when the redirect is to a more complicated route, but generally this is a just a more effective use of the Rails API. Shades of 'refactor to the standard library'!

Another change which increases the routes file size but reduces the number of routes generated is to add only blocks where appropriate. I've found that many resources don't need a destroy action since that's handled in an admin app. So add only blocks and reduce your rake routes output.

There are other cleanups which could be made around shared constraints, unnecessary controller specifications, and redundant path_names usage. But I feel like usage of these features is rare enough that if you're using them you're probably reading the documentation.

When making changes like this, one option is to do a clean sweep and tidy up everything at once. I can see why people do this; it's one code review, one deploy, etc. But I usually do these things piecemeal - one or two changes at a time. I do this because there's lower risk; if I'm only changing a few things it's easy to locate the culprit if something goes wrong. It also lets me do the work in small chunks; instead of devoting a full day to routes file cleanup I can do 10 minutes of work here and there whenever I have time. Also, small changes are more helpful for co-workers to see what's going on. If someone sees a commit with a small and straightforward route cleanup, that person might be more likely to do a similar cleanup next time they see a similar issue. And if I'm headed down a wrong road with a cleanup strategy, someone can catch it earlier on in the process before I do too much damage.