Rails Patch: Change the Name of the :id Parameter in Routing Resources

A relatively little known fact about the rails routing helpers is that they use the ActiveRecord#to_param method for the id value. This means you can have the to_param method of a user object return the username, and access that resource as /users/georgebushlol. The problem is that you still have controller methods that look like this:

def show
  @user = User.find_by_username(:id)
end

…when what you’d really like is this:

def show
  @user = User.find_by_username(:username)
end

We submitted a patch to Rails that adds the ability to change the name of the :id parameter in the RESTFull routes. It adds a parameter named :key to the options for map.resources, which specifies a name to be used instead of :id in all member urls.

If the value doesn’t start with the singular name of the resource (such as :name), then '[resource]_' will be prepended to it when nested (more on that later). Here’s an example:

map.resources :clients, :key => :client_name do |client|
  client.resources :sites, :key => :name do |site|
    site.resources :articles, :key => :title
  end
end

These routes create the following paths:

/clients/:client_name
/clients/:client_name/sites/:name
/clients/:client_name/sites/:site_name/articles/:title

Notice that because 'clientname' starts with 'client' (singular version of the controller), the nested path parameter remains 'clientname', and is not automatically set to 'clientclientname' (which would be dumb). The example above shows the full range of the :key option, but common usage would be more in line with this:

map.resources :users, :key => :user_id do |user|
  user.resources :books, :key => :isbn do |book|
    book.resources :authors
  end
end

The path to parameterized path mappings for these routes are:

/users/2 => /users/:user_id
/users/2/books/0-12-345678-9 => /users/:user_id/books/:isbn
/users/2/books/0-12-345678-9/authors/3 => /users/:user_id/books/:book_isbn/authors/:id

In addition, we find that our code can be greatly simplified when the keys in the params hash are always referring to the same models. Normally :id can refer to any of your model classes, depending on which controller you’re in. If you specify a :key value of :XXX_id for each resource, however, then you can move a good deal of your code to helper methods which can be used globally:

def get_user
  @user ||= User.find(params[:user_id])
end

Consistent use of these helpers really helps the readability of you application.

The patch includes pretty good tests, and we’ve been using it for the past few months in three of our production applications. If you’d like to see it in rails 1.3, please head on over and vote for it.

UPDATE: Thanks to Jon for pointing out a typo in a code block above.