Rails Routing

A key component of the framework


September 23, 2007
URL:http://drdobbs.com/open-source/rails-routing/202100607

David A. Black is the author of Rails Routing, from which this article is adapted. Copyright (c) 2008 Pearson Education. All rights reserved.


The Rails routing system does two things: It recognizes URLs and it generates URLs.

Recognizing URLs is useful because it's how your application decides what it's supposed to do when a particular request comes in:

http://localhost:3000/recipes_for/apples    
<i>What do we do now?! </i>

Generating URLs is useful because it allows you to use relatively high-level syntax in your view templates and controllers when you need insert a URL -- so you don't have to do this:


<a href="http://localhost:3000/recipes_for/apples">Apple recipes   
<i>Not much fun having to type this out by hand!</i>

The routing system deals with both of these issues: how to interpret (recognize) a request URL and how to write (generate) a URL.

It performs both of these functions based on rules that you provide. The rules are inserted into the file config/routes.rb, using special syntax. (Actually it's just Ruby program code, but it uses special methods and parameters.)

Each rule -- or, to use the more common term, each route -- includes a pattern string, which will be used both as a template for matching URLs and as a blueprint for writing them. The pattern string contains a mixture of static substrings, forward slashes (it's mimicking URL syntax), and wildcard, positional parameters that serve as "receptors" for corresponding values in a URL, for both recognition and generation purposes.

A route can also include one or more bound parameters, in the form of key/value pairs in a hash.

The fate of these key/value pairs depends on what the key is. A couple of keys (:controller and :action) are "magic," and they determine what's actually going to happen. Other keys (:blah, :whatever, etc.) are stashed in CGI space for future reference. Putting some flesh on the bones of this description, here's a sample route, related to the preceding examples:


map.connect 'recipes_for/:ingredient',
  :controller => "recipes",
  :action => "show"

In this example, you can see:

Routes have a pretty rich syntax -- this one isn't by any means the most complex (nor the simplest) -- because they have to do so much. A single route, such as the preceding one, has to provide enough information both to match an existing URL and to manufacture a new one. The route syntax is engineered to address both of these processes. It's actually not hard to grasp, if you take each type of field in turn. We'll do a run-through using the "ingredient" route. Don't worry if it all doesn't sink in the first time through. We'll be unpacking and expanding on the techniques and details throughout the section. As we go through the route anatomy, we'll look at the role of each part in both URL recognition and URL generation. Keep in mind that this is just an introductory example. You can do lots of different things with routes; but examining this example will give you a good start in seeing how it all works.

Bound Parameters

Recognition. The bound parameters -- key/value pairs in the hash of options at the end of the route's argument list -- determine what's going to happen if and when this route matches an incoming URL. Let's say someone connects to:


http://localhost:3000/recipes_for/apples

This URL will match the ingredient route. The result will be that the show action of the recipes controller will be executed (and the string "apples" will be made available too, as you'll see later).

To see why, look at the route again:


map.connect 'recipes_for/:ingredient',
  :controller => "recipes",
  :action => "show"

The :controller and :action keys are "magic": This route, when matched by a URL, will always take the visitor to exactly that controller and that action. You'll see techniques for matching controller and action based on "wildcard" matching shortly. In this example, though, there's no wildcard involved. The controller and action are hard-coded.

Generation. To generate a URL for use in your code, you provide values for all the necessary bound parameters. That way, the routing system can do enough match-ups to find the route you want. The parameters are usually bundled in a hash. For example, to generate a URL from the ingredient route, you'd do something like this:


<%= link_to "Recipe for apples",
   :controller => "recipes",
   :action => "show",
   :ingredient => "apples" %>

The values "recipes" and "show" for :controller and :action will match the ingredient route, which contains the same values for the same parameters. That means the pattern string in that route will serve as the template -- the blueprint -- for the generated URL. (The use of a hash to specify URL components is common to all the methods that produce URLs (link_to, redirect_to, form_for, etc.). Underneath, these methods are making their own calls to url_for, a lower-level URL generation method that we'll talk about more a little further on.)

We've left :ingredient hanging. It's a wildcard component of the pattern string.

Wildcard Components ("Receptors")

Recognition. The symbol :ingredient inside the quoted pattern in the route is a wildcard or variable. You can think of it as a receptor: Its job is to be latched on to by a value. Which value latches on to which wildcard is determined positionally, lining the URL up with the pattern string:


http://localhost:3000/recipes_for/apples <i>Someone connects to this URL...</i><br>
   'recipes_for/:ingredient' <i>and it matches this pattern string</i>.

The :ingredient receptor, in this example, receives the value "apples" from the URL. For you, this means that the value params[:ingredient] will be set to the string "apples". You can access that value inside your recipes/show action.

Generation. When you generate a URL, you have to supply values that will attach to the receptors -- the wildcard symbols inside the pattern string. You do this using key => value syntax. That's the meaning of the last line in the previous example:


<%= link_to "Recipe for apples",
   :controller => "recipes",
   :action => "show",
   :ingredient => "apples" %>

In this call to link_to, we've provided values for three parameters. Two of them are going to match hard-coded, bound parameters in the route; the third, :ingredient, will be assigned to the slot in the URL corresponding to the :ingredient slot in the pattern string. But they're all just hash key/value pairs. The call to link_to doesn't "know" whether it's supplying hard-coded or wildcard values. It just knows (or hopes!) that these three values, tied to these three keys, will suffice to pinpoint a route -- and therefore, a pattern string, and therefore, a blueprint for a URL.

Static Strings

Recognition. Our sample route contains a static string inside the pattern string: recipes_for. This string anchors the recognition process. When the routing system sees a URL that starts with /recipes_for, it will match that to the static string in the ingredient route. Any URL that does not contain the static string recipes_for in the leftmost slot will not match this route.

Generation. Static strings in the route simply get placed, positionally, in the URL that the routing system generates. Thus, the earlier link_to example will generate:

<a href="http://localhost:3000/recipes_for/apples">Recipes for apples</a>

The string recipes_for did not appear in the link_to call. The parameters of the link_to call triggered a match to the ingredient route. The URL generator then used that route's pattern string as the blueprint for the URL it generated. The pattern string stipulates the substring recipes_for. URL recognition and URL generation, then, are the two jobs of the routing system. It's a bit like the address book stored in a cell phone. When you select "Gavin" from your contact list, the phone figures out the phone number. And when Gavin calls you, the phone figures out from the phone number that the caller is Gavin; that is, it recognizes the number and maps it to the value "Gavin," which is displayed on the phone's screen.

Rails routing is a bit more complex than cell phone routing because variables are involved. It's not just a one-to-one mapping. But the basic idea is the same: Recognize what comes in, and generate what goes into the code.

We're going to turn next to the routing rules themselves. As we go, you should keep the recognition/generation dual purpose in mind. Two principles are particularly useful to remember:

The routing rule then provides the necessary information to trigger a controller action. Someone looking at the URL without knowing the rules won't know what the URL means. You'll see how these play out in detail as we proceed.

The routes.rb File

Routes are defined in the file config/routes.rb. This file is created when you create your application. It comes with a few routes already written. In most cases, you'll want to change and/or add to the routes defined in the file.

As generated by Rails, routes.rb looks like this:

 

 

 

ActionController::Routing::Routes.draw do |map|
  # The priority is based upon order of creation: first created -> highest priority.
  # Sample of regular route:
  # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
  # Keep in mind you can assign values other than :controller and :action
  # Sample of named route:
  # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
  # This route can be invoked with purchase_url(:id => product.id)
  # You can have the root of your site routed by hooking up ''
  # -- just remember to delete public/index.html.
  # map.connect '', :controller => "welcome"
  # Allow downloading Web Service WSDL as a file with an extension
  # instead of a file named 'wsdl'
  map.connect ':controller/service.wsdl', :action => 'wsdl'
  # Install the default route as the lowest priority.
  map.connect ':controller/:action/:id.:format'
  map.connect ':controller/:action/:id'
end

The whole thing consists of a single call to the method ActionController::Routing::Routes.draw. That method takes a block; and everything from the second line of the file to the second-to-last line is that block.

Inside the block, you have access to a variable called map. This variable is an object of the class ActionController::Routing::RouteSet::Mapper. It's what gives you leverage on the routing system: You define routing rules by calling methods on your mapper object. In the default routes.rb file, you see several calls to map.connect. Each such call (at least, those that aren't commented out) creates a new route by registering it with the routing system. The routing system has to find a pattern match for a URL it's trying to recognize, or a parameters match for a URL it's trying to generate. It does this by going through the rules -- the routes -- in the order in which they're defined; that is, the order in which they appear in routes.rb. If a given route fails to match, the matching routine falls through to the next one. As soon as any route succeeds in providing the necessary match, the search ends.

The Default Route

If you look at the very bottom of routes.rb you'll see the default route:


map.connect ':controller/:action/:id'

The default route is, in a sense, the end of the journey; it defines what happens when nothing else happens. However, it's also a good place to start. If you understand the default route, you'll be able to apply that understanding to the more intricate examples as they arise.

Default Route Recognition

The default route consists of just a pattern string, containing three wildcard "receptors." Two of the receptors are the magic :controller and :action. That means this route determines what it's going to do based entirely on wildcards; there are no bound parameters, no hard-coded controller or action.

Here's a sample scenario. A request comes in with the URL:


http://localhost:3000/auctions/show/1

Let's say it doesn't match any other pattern. It hits the last route in the file -- the default route. There's definitely a congruency, a match. We've got a route with three receptors, and a URL with three values, and therefore, three positional matches:


:controller/:action/:id
   auctions / show / 1

We end up, then, with the auctions controller, the show action, and 1 for the id value (to be stored in params[:id]). The dispatcher now knows what to do. The behavior of the default route illustrates some of the specific default behaviors of the routing system. The default action for any request, for example, is index. And given a wildcard such as :id in the pattern string, the routing system prefers to find a value for it, but will go ahead and assign it nil rather than give up and conclude that there's no match. Table 1 shows some examples of URLs and how they will map to this rule, and with what results.

Table 1: Sample URLs and Their Routing

(The nil in the last case is probably an error because a show action with no id is usually not what you'd want!)

Spotlight on the :id Field

Note that the treatment of the :id field in the URL is not magic; it's just treated as a value with a name. If you wanted to, you could change the rule so that :id was :blah -- but then you'd have to remember to do


@auction = Auction.find(params[:blah])

in your controller action. The name :id is simply a convention. It reflects the commonness of the case where a given action needs access to a particular database record. The main business of the router is to determine the controller and action that will be executed. The id field is a bit of an extra; it's an opportunity for actions to hand a data field off to each other. The id field ends up in the params hash, which is automatically available to your controller actions. In the common, classic case, you'd use the value provided to dig a record out of the database:


class ItemsController < ApplicationController
  def show
    @item = Item.find(params[:id])
  end
end

In addition to providing the basis for recognizing URLs and triggering the correct behavior, the default route also plays a role in URL generation. Here's a link_to call that will use the default route to generate a URL similar to the one we submitted for recognition in the previous example:


<%= link_to item.description,
   :controller => "item",
   :action => "show",
   :id => item.id %>

This code presupposes a local variable called item, containing (we assume) an Item object. The idea is to create a hyperlink to the show action for the item controller, and to include a "drill-down" to the id of this particular item. The hyperlink, in other words, will look something like this:


<a href="localhost:3000/item/show/3"> A signed picture of Houdini</a>

This URL gets created courtesy of the route generation mechanism. Look again at the default route:


map.connect ':controller/:action/:id'

In our link_to call, we've provided values for all three of the fields in the pattern. All that the routing system has to do is plug in those values:


item/show/3

and insert the result into the URL. When someone clicks on the link, that URL will be recognized -- courtesy of the other half of the routing system, the recognition facility -- and the correct controller and action will be executed, with params[:id] set to 3.

The generation of the URL, in this example, uses wildcard logic: We've supplied three symbols, :controller, :action, and :id, in our pattern string, and those symbols will be replaced, in the generated URL, by whatever values we supply. Contrast this with our earlier example:


map.connect 'recipes_for/:ingredient',
   :controller => "recipes",
   :action => "show"

To cause the URL generator to select this route as its blueprint, you have to specify the exact words "recipes" and "show" for :controller and :action in the parameters you supply to link_to. In the case of the default route -- and, indeed, any route that has symbols embedded in its pattern -- you still have to match, but you can use any value.

Modifying the Default Route

A good way to get a feel for the routing system is to change things and see what happens. We'll do this with the default route. You'll probably want to change it back, but changing it will show you something about how routing works.

Specifically, swap :controller and :action in the pattern string:


# Install the default route as the lowest priority.
map.connect ':action/:controller/:id'

You've now set the default route to have actions first. That means that where you might previously have connected to http://localhost:3000/auctions/show/3, you'll now need to connect to http://localhost:3000/show/auctions/3. And when you generate a URL from this route, it will come out in the /show/auctions/3 order.

It's not particularly logical; the original default (the "default" default) route is better. But it shows you a bit of what's going on, specifically with the magic symbols :controller and :action. Try a few more changes and see what effect they have. (And then put it back the way it was!)

The Ante-Default Route and respond_to

The route just before the default route (thus, the "ante-default" route) looks like this:


map.connect ':controller/:action/:id.:format'

The .:format at the end matches a literal dot and a wildcard "format" value after the id field. That means it will match, for example, a URL like this:


http://localhost:3000/recipe/show/3.xml

Here, params[:format] will be set to xml. The :format field is special; it has an effect inside the controller action. That effect is connected with a method called respond_to. The respond_to method allows you to write your action so that it will return different results, depending on the requested format. Here's a show action for the items controller that offers either HTML or XML:


def show
  @item = Item.find(params[:id])
    respond_to do |format|
      format.html
      format.xml { render :xml => @item.to_xml }
    end
  end

The respond_to block in this example has two clauses. The HTML clause consists solely of format.html. A request for HTML will be handled by the usual rendering of the RHTML view template. The XML clause includes a code block; if XML is requested, the block will be executed and the result of its execution will be returned to the client. Here's a command-line illustration, using wget (slightly edited to reduce line noise):

$ wget http://localhost:3000/items/show/3.xml -O -
Resolving localhost... 127.0.0.1, ::1
Connecting to localhost|127.0.0.1|:3000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 295 [application/xml]
<item>
  <created-at type="datetime">2007-02-16T04:33:00-05:00</created-at>
  <description>Violin treatise</description>
  <id type="integer">3</id>
  <maker>Leopold Mozart</maker>
  <medium>paper</medium>
  <modified-at type="datetime"></modified-at>
  <year type="integer">1744</year>
</item>

The .xml on the end of the URL results in respond_to choosing the "xml" branch, and the returned document is an XML representation of the item.

respond_to and the HTTP-Accept Header

You can also trigger a branching on respond_to by setting the HTTP-Accept header in the request. When you do this, there's no need to add the .:format part of the URL. Here's a wget example that does not use .xml but does set the Accept header:

wget http://localhost:3000/items/show/3 -O - --header="Accept: text/xml"
Resolving localhost...127.0.0.1, ::1
Connecting to localhost|127.0.0.1|:3000... connected.
HTTP request sent, awaiting response...
200 OK
Length: 295 [application/xml]
<item>
  <created-at type="datetime">2007-02-16T04:33:00-05:00</created-at>
  <description>Violin treatise</description>
  <id type="integer">3</id>
  <maker>Leopold Mozart</maker>
  <medium>paper</medium>
  <modified-at type="datetime"></modified-at>>
  <year type="integer">1744</year>
</item>

The result is exactly the same as in the previous example.

The Empty Route

Except for learning-by-doing exercises, you're usually safe leaving the default route alone. But another route in routes.rb plays something of a default role that you will probably want to change: the empty route.

A few lines up from the default route you'll see this:

# You can have the root of your site routed by hooking up ''
# -- just remember to delete public/index.html.
# map.connect '', :controller => "welcome"

What you're seeing here is the empty route -- that is, a rule specifying what should happen when someone connects to:

http://localhost:3000 Note the lack of "/anything" at the end!

The empty route is sort of the opposite of the default route. Instead of saying, "I need any three values, and I'll use them as controller, action, and id," the empty route says, "I don't want any values; I want nothing, and I already know what controller and action I'm going to trigger!" In a newly generated routes.rb file, the empty route is commented out because there's no universal or reasonable default for it. You need to decide what this "nothing" URL should do for each application you write.

Here are some examples of fairly common empty route rules:

map.connect '', :controller => "main", :action => "welcome"
map.connect '', :controller => "top", :action => "login"
map.connect '', :controller => "main"

That last one will connect to main/index -- index being the default action when none is specified. Defining the empty route is a nice way to give people something to look at when they connect to your site with nothing but the domain name.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.