Rails 101: Routing
December 28, 2008

Routes define what URLs are used to access different parts of your application.
When you run the scaffold generator described in the previous article routes are setup for you automatically but they only setup the default settings, sometimes you need something a little more complex.
In this article, I will show you how routes work and how to set them up yourself. You can download a video version of this article, however the article is more up-to-date.
How Routes Work
You might have heard the phrase ‘RESTful routes’, REST stands for Representational State Transfer and basically makes use of different methods of request to process different actions, this way applications can interact with one another very easily over the standard HTTP protocol.
So how does REST affect you? Well if you are familiar with HTML you will know that forms have a method attribute which is set to GET or POST, however there are actually a few more methods available to HTTP but not included in HTML, these include the PUT and DELETE methods, REST uses these to figure out whether you are trying to create, read, update or delete an item.
POST /users => Create
GET /users/1 => Read (show)
PUT /users/1 => Update
DELETE /users/1 => Delete (destroy)
The standard Rails setup needs a few more actions than these, for starters there is the index action which lists all the items in the collection, this can be accessed by GETing the collection URL, in this example at/users. Other methods by default suffix the collection URL or object URL with their name, however this behavior can be overridden – but we’ll look into that later.
GET /users => Index
GET /users/new => New
GET /users/1/edit => Edit
Putting The Magic To Work
The routes file is located within the config directory, by default it contains just two routes, both of which aren’t restful and simply act as catch all for non-nested URLs, personally I like to remove these as they let you get away with not setting up your routes properly.
To make our RESTful users section, all you need to enter is map.resources :users, pretty simple right? Rails uses this one command to produce a set of around 14 routes!
You can now test this with a rake command: rake routes.
If you look carefully, you’ll notice that each route has a name associated with it, such as new_user, this becomes useful in our views and controllers where we need to reference the URLs dynamically. In views you suffix the name with ‘_path’, for example new_user_path and in controllers you should use ‘_url’, for example new_user_url, this is because we only want the path in the view (/something), but in controllers you want the full address for use in the API (http://…).
You can setup named routes manually by typing the name of your route after map. When setting up routes manually this way you have to specify a string which is parsed for parameters, the parameters are words prefixed with a colon, like symbols in Ruby.
For an example, here’s a route to disable a user, the route will be called disable_user, and the route will include the user’s ID, and it will call the disable method of the users controller.
map.disable_user 'users/:user_id/disable',
:controller => 'users',
:action => 'disable'
As you can see, ‘user_id’ prefixed with a colon, this means it’s a parameter and can be reference using params[:user_id]. You can also constrict the parameter using a regular expression, so if we only wanted to accept numbers:
map.disable_user 'users/:user_id/disable',
:controller => 'users',
:action => 'disable',
:user_id => /\d/
Remember I mentioned those additional HTTP methods before? Well we can constrain by them too, by adding the conditions hash to the end containing only one key value pair, I’ll choose “PUT” which describes updating a current record.
map.disable_user 'users/:user_id/disable',
:controller => 'users',
:action => 'disable',
:user_id => /\d/,
:conditions => { :method => :put }
If you are going down the RESTful route (no pun intended), you generally won’t be writing your own routes like this very often, most of the time you can add them to the map.resources, so if we return back to our users entry, we can add our disable method to the member hash, which means it acts on one object, not the whole collection. If we wanted a disabled action to list all disabled users we can add it to the collection hash.
map.resources :user, :member => { :disable => :put }
Nested Routes
Another bonus for using map.resources is you can easily nest resources, so for example if a user has a profile, I would nice to be able to have routes like /users/1/profile, and to do this we can just change the current map.resources into a block and drop another map.resource within it, but you don’t use map this time, you use the word between the goal posts, in this case ‘users’.
map.resources :user, :member => { :disable => :put } do |users|
users.resource :profile
end
We can even refactor this further using the :has_one option:
map.resources :user, :member => { :disable => :put }, :has_one => :profile
There is also a similar option for has_many.
You can now access the profile using more route helpers such as: edit_user_profile_path(user_id)
Special Notes
There are a few special things to note about routes, and those are the root route (again, no pun intended), path prefixing, namespacing, route globbing and shallow routing.
Root URLs
When a user goes to the root URL of your site you often want users to be greeted with a homepage, or dashboard, so to do that simply create a named route called root without a URL string attached to it. In this example, I’ll make it point to the index of the users controller.
map.root :controller => 'users', :action => 'index'
Path Prefixes
Path prefixing allows you to prefix resource URLs with a string such as ‘account’, to make /account/users, this means that you can group various URLs under one section.
map.resources :user, :member => { :disable => :put }, :path_prefix => 'account'
Namespacing
If you have a bunch of controllers that have duplicate names but differing functionality, for example having a front-end ‘notes’ controller which only lists and displays notes, and a back-end one which is protected and has a different layout, then namespacing keeps things tidy.
You will need to group your routes within a namespace block as below:
namespace(:admin) do |admin|
map.resources :user
# ...
end
And your controller will have to be namespaced too:
Admin::NotesController < ApplicationController
Route Globbing
For route globbing, or in other words catch all routes with an unlimited nesting depth (i.e /random/url/here), you can prefix the parameter with an splat (asterisk) instead of a colon and in your controller and view the parameter will return an array of the parts.
map.page '*path', :controller => 'users', :action => 'page'
Shallow Routing
Recently introduced into Rails is the :shallow option, this allows you to access nested routes as if they were belonging to the root. For example the routes:
map.resources :notes, :shallow => true do |note|
note.resources :comments
end
Would allow you to use the routes comments_path and comment_path(1) rather than having to use the nested paths note_comments_path(note_id) or note_comment_path(note_id, 1).
Summary
To create RESTful routes, you can use map.resources or resource, depending on whether you want a collection, or singular resource respectively. You can use the :member or :collection options to map additional routes within the resource, :has_many or :has_one to specify sub-routes and :path_prefix to add string prefixes to your paths.
For the homepage you should use map.root which negates the URL option, as it defaults to an empty string.
And for other named routes you simply type map. followed by the name and the URL as a string, for example map.user 'users/:id' then follow it with a hash of options for :controller, :action and any constraints.
2 people have voiced their opinions, voice yours?
Neil Cauldwell said:
Cool, there’s always parts of Rails which you just don’t understand fully AND which could help you out at some point (there’s some bits you just don’t need to know about), and routing is an important part which does provide benefits on a daily basis. Member and collection hashes are particularly handy.
Ryan, do you know much about the performance hits incurred by creating routing helpers? I remember hearing something along the lines of them being one of the heftier parts of the framework, not that that has stopped me using them.
Also, have you heard about shallow routing in the next version of Rails? It looks very handy, but, there does seem to be a couple of potential issues, and they were pointed in this post (Google bots + I thought I would rather specify :shallow next to each nested resource, as opposed to just the most parental);
http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes
Edit | Destroy
Ryan Townsend said:
I don’t believe there should be any performance issues with using routing helpers, as they should be only loaded on spin up of your application, not on a per request basis.
I’ve updated the article with the new shallow routing.
Edit | Destroy