- The Two Purposes of Routing
- Bound Parameters
- Wildcard Components ("Receptors")
- Static Strings
- The routes.rb File
- The Ante-Default Route and respond_to
- The Empty Route
- Writing Custom Routes
- Using Static Strings
- Using Your Own "Receptors"
- A Note on Route Order
- Using Regular Expressions in Routes
- Default Parameters and the url_for Method
- Using Literal URLs
- Route Globbing
- Globbing Key-Value Pairs
- Named Routes
- What to Name Your Routes
- The Special Scope Method with_options
- Conclusion
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!)