Understanding Rails Routes

by Melvin Ram

The config/routes.rb file is an important piece of your app. In this post, I’ll go through the standard file that is generated when you create a new Rails project. Let’s go through each section one at a time.

# The priority is based upon order of creation: first created -> highest priority.

At this comment suggests, routes specified higher up in the file have highest priority. This means if you have two routes that handle the same incoming request, the first one will work and the second one will be ignored.

Structure of a route

map.connect 'products/:id', :controller => 'catalog', :action => 'view'

With this route, if someone visits /products/1, Rails will call the ‘view’ action in the ‘catalog’ controller and will pass it the value of 1 as params[:id]. Notice the structure of this route:

map.SOMETHING URL_TO_HANDLE, WHERE_TO_ROUTE_THIS_REQUEST

As we go through more examples, pay attention to these groups. Now one thing the above route won’t do for you is make it easy to reference this url from a link_to or from a form_for. That’s what the named routes are for.

Named Routes

map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'

Named routes allow you to reference the route throughout your app. For example, you could put this inside a view

<%= link_to "Buy Now", purchase_url(:id => product.id) %>

and it would output a link that would be handled by the above route:

<a href="http://domain/products/12/purchase">Buy Now</a>

You could also use it inside a controller to redirect to this page with:

:redirect_to purchase_url(:id => @product.id)

Get the idea? Good. Let’s move on to resources.

Resources in Routes.rb

map.resources :products

The above one line of code gives you a bunch of named routes:

= GET routes
1. products_path         /products
2. product_path(1)       /product
3. new_product_path      /products/new
4. edit_product_path(1)  /products/1/edit
= POST Routes
5. products_path(:method => :post)    /products
6. product_path(1, :method => :post)  /products/1
= DESTROY Route
7. product_path(1, :method => :destroy)  /products/1

These 7 routes allow you to tell your application what it should do (create, read, update or delete records) using just HTTP requests. And you get all that with one line in your routes.rb. Cool, huh? For more info, google “restful rails“.

:member & :collection routes for Resources

Okay great. But what if you want to do things that isn’t included by default in a resource? That’s what member & collections options are for.

map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }

:member & :collection allow you to add additional named routes to your resources. Let’s start with :collection. Collection adds routes to the entire collection. If you had a resource of Contacts, a collection route you might add would be delete_all_older_than_6_months and the route to get to it would be be /contacts/delete_all_older_than_6_months/. A member method applies only to one record. For example, you might add a member method called upgrade_to_vip that would update a specific contact to have a status of VIP. And the route would be /contacts/1/upgrade_to_vip. Even though Rails makes it easy to add member & collection routes to your resource, you should think twice before adding one. Why? Because most likely, you can achieve whatever your trying to do with just the 7 default routes. Alright, now lets move to nested resources.

Routes for Nested Resources

map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller

The above nested resources allow you to do:

new_product_comment_path(1)   /products/1/comments/new
product_comment_path(1, 1)    /products/1/comments/1
product_seller_path(1)        /products/1/seller

The :has_many & :has_one options are great for simple nested resources, but what if you need to add more options to the resources that are nested? In that case, instead of using :has_many & :has_one, you’ll use a block.

map.resources :products do |products|
  products.resources :comments
  products.resources :sales, :collection => { :recent => :get }
end

This will allow you to add :member & :collection routes as well as other specifics to each nested resource.

Namespaces in Routes

map.namespace :admin do |admin|
  admin.resources :products
end

admin_products_path # goes to /admin/products

Let’s say you have an ecommerce web app. You might want the admin view of the products page to be different than the normal view. Route namespaces allow you to do that.

# Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)

As the comment indicates, requests on resources within a namespace don’t get directed to ProductsController what would normally be located at app/controllers/products_controller.rb.  Instead, it directs the request to Admin::ProductsController which needs to located at app/controllers/admin/products_controller.rb. Why couldn’t you just use nested resources? Because admin is not a resource (it doesn’t have a table inside the database).

Root Route

map.root :controller => "welcome"

map.root is a special route that handles http requests for ‘/’. It’s used to specify what you want to show as your home page.

Rake Routes

# See how all your routes lay out with "rake routes"

If you run “rake routes”, you’ll see a list of all your routes with columns for: route name, HTTP method, route path & route requirements. It’s a good way to do a sanity check that your routes were properly setup.

Default Routes

# Install the default routes as the lowest priority.
# Note: These default routes make all actions in every controller accessible via GET requests. You should
# consider removing the them or commenting them out if you're using named routes and resources.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

The default routes are there to catch routes that haven’t been specified. It’ll try to guess the controller & action based on the URL that is requested. It’s much less useful now that resources have become the dominant way of doing things in Rails.

Leave a Comment