Routing in Rails 3

This post deals with the basics of routing in Rails 3. If you want to look at the more advanced RESTful routing, take a look at RESTful Routing in Rails 3.

Routing has been one of the topics in Rails that I have always found rather confusing. My confusion was probably not helped by the routing configuration having undergone some substantial changes in the different versions of Rails.

Having now read ‘The Rails 3 Way’ and the excellent discussions on routing I would like to revisit this topic and see if it makes a bit more sense.

Basics first

The Routing system in Rails examines the URL of an incoming request and determines what action should be taken by the application. Usually this means a URL will be mapped to a specific action on a specific controller.

Let’s start off with the most common example and then dissect it. The following line of code would live inside your routes configuration, located at config/routes.rb.

match ‘product/:id’ => ‘products#show’

This route will match any URL which contains the word ‘product’ followed by a slash and an identifier to the show method on the ProductsController class. Even though this single line of code is a relatively straightforward piece of configuration, there is actually a lot going on.

  • match – this refers to the verb we’re expecting in the HTTP request. You could either use get or post here, but match will work on both.
  • product – this is a static string. This route will only match a URL which contains this string. We can also change this static string without affecting other parts of the application.
  • / – a slash, mimicking an actual URL
  • :id – a placeholder or variable, known as a segment key. The routing module will match this part of the incoming URL and make it available within the relevant controller method (the show method on the products controller in this case) through the params method – params[:id].

The code listed above is actually shorthand for the following:

match ‘product/:id’, :to => ‘products#show’

Because this is such a common scenario, the shorthand was introduced.

Segment Keys

In the example listed above we used a variable, :id, which in routing is known as a segment key. A common scenario is to use the id being passed in to lookup an object. So if we extend the example above,

match ‘product/:id’ => ‘products#show’

we might have a controller which looks like this:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  end
end

We can also have multiple segment keys in a single route, for example:

match ‘product/:category/:id’ => ‘products#show’

In this case we would be able to access the category by using params[:category] and the id by using params[:id].

There is also nothing magic about using the identifier named :id – we could use any variable name here. For example, we could define the following route instead:

match ‘product/:product_id’ => ‘products#show’

Which would mean we would access the variable by using params[:product_id]. Easy!

You can even hardcode additional parameters which don’t have any effect on the URL matching, but will be available through the params method.

match ‘product/sale/:id’ => ‘products#show’, :sale => true

I wouldn’t recommend this approach though – rather map to a different action.

Constraining the Request Method

In the example above I’m not constraining the HTTP verb at all – which means the routes will match on any verb. If we want to change the route to only match when an HTTP get is used, we can do the following:

match ‘product/:id’ => ‘products#show’, :via => :get

Once again, because this is such a common use case, Rails introduces the following shorthand:

get ‘product/:id’ => ‘products#show’

You can do the same for any HTTP verb. We can also filter on multiple verbs, for example – if we wanted to constrain the route to get or post, we would do the following:

match ‘product/:id’ => ‘products#show’, :via => [:get, :post]

Named Routes

The routing system does two things: it maps incoming requests to the correct controller and action, and it enables the dynamic generation of URLs for you to use within your views as arguments to methods like link_to and redirect_to. Named routes are also core to the RESTful nature of Rails routing – more on that later.

We can name a route by using an optional :as parameter in a rule:

match ‘/products’ => ‘products#index’, :as => ‘products’

In this example the routing system will generate two methods – products_url and products_path. You can use these methods anywhere Rails expects a URL.

link_to ‘All Products’, products_path

We commonly only use the path method (products_path in this case), but the only difference is that the url method generates a full URL while the path method only generates a relative one. I usually stick with the path method unless I’m doing something like exporting data where I need a permalink.

The natural question is: What about routes with segment keys (variables)? And the natural answer is: We need to pass them in. Let’s try to create a named route from our previous example:

match ‘product/:id’ => ‘products#show’, :as => ‘product’

As before, this rule will generate two methods – product_url and product_path. However, since this rule has a segment key, we need to provide the missing parameter.

link_to “Show details for #{product.name}”, product_path(:id => product.id)

Easy! Actually, we can simplify this a little. Remember when I said there is nothing magic about using the parameter name :id? Well in this case, it is a little magic. If you need to supply an :id as an argument to a named route, you can simply supply the value, without having to specify the :id key:

link_to “Show details for #{product.name}”, product_path(product.id)

In fact, Rails takes this a step further – you can (and you probably should) supply an object and Rails will grab the id automatically:

link_to “Show details for #{product.name}”, product_path(product)

This applies even when you have multiple segment keys, for example, if we have the following rule:

match ‘category/:category/product/:product’ => ‘products#show’, :as => ‘category_product’

We can pass in two objects, without having to specify any keys:

link_to “Show details for #{category.name} – #{product.name}”, category_product_path(category,product)

Behind the scenes Rails is calling to_param on the arguments you’re passing into the helper methods. This is quite useful to know, since you can easily override the to_param method in your objects to change what is being passed back to the helpers. For example, blogging platforms create something called a slug for URLs – the id is not displayed in the URL – rather, a descriptive, unique string is displayed which is also stored along with the blog object (in order to retrieve it).

There’s much more to it

What I’ve covered here is really only the basics of routing in Rails 3. If you dig deeper you will find some interest concepts – such as globbing – which can be very useful in some niche scenarios. However, one of the most-featured concepts in Rails routing is the RESTful nature of the routes. Since this is a pretty big concept to tackle, I’m going to discuss that in my next post.

Stay tuned. Happy coding.