Home > Articles > Programming > Ruby

Routing the Rails Way

Obie Fernandez
  • PrintPrint
  • Share ThisShare This
  • DiscussDiscuss
Close WindowObie Fernandez

Obie Fernandez

Learn more…

Sorry, this author hasn't posted any blogs.

Rails Way, The

This chapter is from the book
Rails Way, The

Rails routing can be a bit of a tough nut to crack. But it turns out that most of the toughness resides in a small number of concepts. After you've got a handle on those, the rest falls into place nicely. This chapter introduces you to the principal techniques for defining and manipulating routes.

The routes.rb File

Routes are defined in the file config/routes.rb, as shown (with some extra comments) in Listing 3.1. This file is created when you first create your Rails application. It comes with a few routes already written and in most cases you'll want to change and/or add to the routes defined in it.

Listing 3.1. The Default routes.rb File

ActionController::Routing::Routes.draw do |map|
  # The priority is based upon order of creation
  # First created gets 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 body of that block.

Inside the block, you have access to a variable called map. It's an instance of the class ActionController::Routing::RouteSet::Mapper. Through it you configure the Rails 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.

The default route consists of just a pattern string, containing three wildcard "receptors." Two of the receptors are :controller and :action. That means that 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 like :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 3.1 shows some examples of URLs and how they will map to this rule, and with what results.

Table 3.1. Default Route Examples

URL

Result

Value of id

Controller

Action

/auctions/show/3

auctions

show

3

/auctions/index

auctions

index

nil

/auctions

auctions

index (default)

nil

/auctions/show

auctions

show

nil probably an error!

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 this in your controller action:

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

The name :id is simply a convention. It reflects the commonness of the case in which 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

Default Route Generation

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:

<%= 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 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 and insert the result into the URL:

item/show/3

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/:ingredient',
            :controller => "recipes",
            :action => "show"

To get the URL generator to choose this route, you have to specify "recipes" and "show" for :controller and :action when you request a URL for link_to. In 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 by changing things and seeing 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 previously you might 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!)

  • Share ThisShare This
  • Your Account

Discussions

Make a New Comment

You must log in in order to post a comment.

Related Resources

Danny KalevMinutes from the October 2009 Meeting
By Danny Kalev on November 19, 2009 No Comments

The minutes from the Santa Cruz (October 2009) meeting are available here. Even if you're not a language layer at heart, I encourage you to read them.

Danny KalevA Reader's Opinion on Attributes
By Danny Kalev on October 20, 2009 No Comments

In August I dedicated a series to the debate about C++0x attributes. I believe that it covered the subject in a balanced and detailed way, but I keep getting complaints from C++ users who don't like attributes for various reasons. Here's a recent email I received from a Polish C++ programmer. While it  doesn't represent my opinion about attributes -- I'm rather neutral about this feature and consider it a "solution waiting for a problem" -- but it suggests that attributes are still a highly controversial issue that will haunt C++ for a long time. The email is quoted here with minor edits that and as usual, with all private details removed.

Danny KalevFollowup: The Web 2.0 Guy I Ain't
By Danny Kalev on October 16, 2009 1 Comment

Almost a year ago, I posted here The Web 2.0 Guy I Ain't. People wonder whether I still resist all those Web 2.0 features and technologies at the end of 2009.

See All Related Blogs

Informit Network