Before REST came I (and pretty much everyone else) never really knew where to put stuff. | ||
--Jonas Nicklas on the Ruby on Rails mailing list |
With version 1.2, Rails introduced support for designing APIs consistent with the REST style. Representational State Transfer (REST) is a complex topic in information theory, and a full exploration of it is well beyond the scope of this chapter.[1] We‘ll touch on some of the keystone concepts, however. And in any case, the REST facilities in Rails can prove useful to you even if you’re not a REST expert or devotee.
The main reason is that one of the inherent problems that all web developers face is deciding how to name and organize the resources and actions of their application. The most common actions of all database-backed applications happen to fit well into the REST paradigm—we’ll see what that means in a moment.
REST is described by its creator, Roy T. Fielding, as a network “architectural style,” specifically the style manifested in the architecture of the World Wide Web. Indeed, Fielding is not only the creator of REST but also one of the authors of the HTTP protocol itself—REST and the web have a very close relationship.
Fielding defines REST as a series of constraints imposed upon the interaction between system components: Basically, you start with the general proposition of machines that can talk to each other, and you start ruling some practices in and others out by imposing constraints.
The REST constraints include (among others)
Use of a client-server architecture
Stateless communication
Explicit signaling of response cacheability
The World Wide Web allows for REST-compliant communication. It also allows for violations of REST principles; the constraints aren’t always all there unless you put them there. But Fielding is one of the authors of the HTTP protocol; and while he has some criticisms of the protocol from the REST point of view (as well as criticisms of widespread non-REST-compliant practices, such as the use of cookies), the overall fit between REST and the web is not a coincidence.
REST is designed to help you provide services, and to provide them using the native idioms and constructs of HTTP. You’ll find, if you look for it, lots of discussion comparing REST to, for example, SOAP—the thrust of the pro-REST argument being that HTTP already enables you to provide services, so you don’t need a semantic layer on top of it. Just use what HTTP already gives you.
One of the payoffs of REST is that it scales relatively well for big systems, like the web. Another is that it encourages—mandates, even—the use of stable, long-lived identifiers (URIs). Machines talk to each other by sending requests and responses labeled with these identifiers. Those requests and responses also contain representations (manifestations in text, XML, graphic format, and so on) of resources (high-level, conceptual descriptions of content). Ideally at least, when you ask a machine for an XML representation of a resource—say, Romeo and Juliet—you’ll use the same identifier every time and the same request metadata indicating that you want XML, and you’ll get the same response. And if it’s not the same response, there’s a reason—like, the resource you’re retrieving is a changeable one (“The current transcript for Student #3994,” for example).
We’ll look at resources and representations further a little later on. For now, though, let’s bring Rails back into the picture.
The REST support in Rails consists of helper methods and enhancements to the routing system, designed to impose a particular style and order and logic on your controllers and, consequently, on the way the world sees your application. There’s more to it than just a set of naming conventions (though there’s that too). We’ll get to details shortly. In the large scheme of things, the benefits that accrue to you when you use Rails’ REST support fall into two categories:
Convenience and automatic best practices for you
A REST interface to your application’s services, for everyone else
You can reap the first benefit even if you’re not concerned with the second. In fact, that’s going to be our focus here: what the REST support in Rails can do for you in the realm of making your code nicer and your life as a Rails developer easier.
This isn’t meant to minimize the importance of REST itself, nor the seriousness of the endeavor of providing REST-based services. Rather, it’s an expedient; we can’t talk about everything, and this section of the book is primarily about routing and how to do it, so we’re going to favor looking at REST in Rails from that perspective.
Moreover, the relationship between Rails and REST, while a fruitful one, is not free of difficulties. Much Rails practice is noncompliant with the precepts of REST from the beginning. REST involves stateless communication; every request has to contain everything necessary for the recipient to generate the correct response. But pretty much every nontrivial Rails program in the world uses server state to track sessions. To the extent that they do, they are not adhering to the REST design. On the client side, cookies—also used by many Rails applications—are singled out by Fielding as a non-REST-compliant practice.
Untangling all the issues and dilemmas is beyond our scope here. Again, the focus will be on showing you how the REST support works, and opening the door to further study and practice—including the study of Fielding’s dissertation and the theoretical tenets of REST. We won’t cover everything here, but what we do cover will be “onward compatible” with the wider topic.
The story of REST and Rails starts with CRUD...
The acronym CRUD (Create Read Update Delete) is the classic summary of the spectrum of database operations. It’s also a kind of rallying cry for Rails practitioners. Because we address our databases through abstractions, we’re prone to forget how simple it all is. This manifests itself mainly in excessively creative names for controller actions. There’s a temptation to call your actions add_item
and replace_email_address
and things like that. But we needn’t, and usually shouldn’t, do this. True, the controller does not map to the database, the way the model does. But things get simpler when you name your actions after CRUD operations, or as close to the names of those operations as you can get.
The routing system is not wired for CRUD. You can create a route that goes to any action, whatever the action’s name. Choosing CRUD names is a matter of discipline. Except... when you use the REST facilities offered by Rails, it happens automatically.
REST in Rails involves standardization of action names. In fact, the heart of the Rails’ REST support is a technique for creating bundles of named routes automatically—named routes that are hard-programmed to point to a specific, predetermined set of actions.
Here’s the logic. It’s good to give CRUD-based names to your actions. It’s convenient and elegant to use named routes. The REST support in Rails gives you named routes that point to CRUD-based action names. Therefore, using the REST facilities gives you a shortcut to some best practices.
“Shortcut” hardly describes how little work you have to do to get a big payoff. If you put this:
map.resources :auctions
into your routes.rb
files, you will have created four named routes, which, in a manner to be described in this chapter, actually allow you to connect to seven controller actions. And those actions have nice CRUD-like names, as you will see.
The term “resources” in map.resources
deserves some attention.
The REST style characterizes communication between system components (where a component is, say, a web browser or a server) as a series of requests to which the responses are representations of resources.
A resource, in this context, is a “conceptual mapping” (Fielding). Resources themselves are not tied to a database, a model, or a controller. Examples of resources include
The current time of day
A library book’s borrowing history
The entire text of Little Dorrit
A map of Austin
The inventory of a store
A resource may be singular or plural, changeable (like the time of day) or fixed (like the text of Little Dorrit). It’s basically a high-level description of the thing you’re trying to get hold of when you submit a request.
What you actually do get hold of is never the resource itself, but a representation of it. This is where REST unfolds onto the myriad content types and actual deliverables that are the stuff of the web. A resource may, at any given point, be available in any number of representations (including zero). Thus your site might offer a text version of Little Dorrit, but also an audio version. Those two versions would be understood as the same resource, and would be retrieved via the same identifier (URI). The difference in content type—one representation vs. another—would be negotiated separately in the request.
Like most of what’s in Rails, the Rails support for REST-compliant applications is “opinionated”; that is, it offers a particular way of designing a REST interface, and the more you play in its ballpark, the more convenience you reap from it. Rails applications are database-backed, and the Rails take on REST tends to associate a resource very closely with an ActiveRecord
model, or a model/controller stack.
In fact, you’ll hear people using the terminology fairly loosely—for instance, saying that they have created “a Book resource.” What they really mean, in most cases, is that they have created a Book
model, a book controller with a set of CRUD actions, and some named routes pertaining to that controller (courtesy of map.resources :books
). You can have a Book
model and controller, but what you actually present to the world as your resources, in the REST sense, exists at a higher level of abstraction: Little Dorrit, borrowing history, and so on.
The best way to get a handle on the REST support in Rails is by going from the known to the unknown—in this case, from the topic of named routes to the more specialized topic of REST.
When we first looked at named routes, we saw examples where we consolidated things into a route name. By creating a route like this...
map.auction 'auctions/:id',
:controller => "auction",
:action => "show"
you gain the ability to use nice helper methods in situations like this:
<%= link_to h(item.description), auction_path(item.auction) %>
The route ensures that a path will be generated that will trigger the show
action of the auctions controller. The attraction of this kind of named route is that it’s concise and readable.
By associating the auction_path
method with the auction/show
action, we’ve done ourselves a service in terms of standard database operations. Now, think in terms of CRUD. The named route auction_path
is a nice fit for a show
(the, um, R in CRUD) action. What if we wanted similarly nicely named routes for the create
, update
, and delete
actions?
Well, we’ve used up the route name auction_path
on the show
action. We could make up names like auction_delete_path
and auction_create_path
... but those are cumbersome. We really want to be able to make a call to auction_path
and have it mean different things, depending on which action we want the URL to point to.
So we need a way to differentiate one auction_path
call from another. We could differentiate between the singular (auction_path
) and the plural (auctions_path
). A singular URL makes sense, semantically, when you’re doing something with a single, existing auction object. If you’re doing something with auctions in general, the plural makes more sense.
The kinds of things you do with auctions in general include creating. The create
action will normally occur in a form:
<% form_tag auctions_path do |f| %>
It’s plural because we’re not saying Perform an action with respect to a particular auction, but rather With respect to the whole world of auctions, perform the action of creation. Yes, we’re creating one auction, not many. But at the time we make the call to our named route, auctions_path
, we’re addressing auctions in general.
Another case where you might want a plural named route is when you want an overview of all of the objects of a particular kind—or, at least, some kind of general view, rather than a display of a particular object. This kind of general view is usually handled with an index
action. Index
actions typically load a lot of data into one or more variables, and the corresponding view displays it as a list or table (possibly more than one).
Here again, we’d like to be able to say:
<%= link_to "Click here to view all auctions", auctions_path %>
Already, though, the strategy of breaking auction_path
out into singular and plural has hit the wall: We’ve got two places where we want to use the plural named route. One is create; the other is index. But they’re both going to look like this:
http://localhost:3000/auctions
How is the routing system going to know that when we click on one, we mean the create
action, and when we click on the other, we mean index
? We need another data-slot, another flag, another variable on which to branch.
Luckily, we’ve got one.
Form submissions are POSTs. Index actions are GETs. That means that we need to get the routing system to realize that
/auctions submitted in a GET request!
versus
/auctions submitted in a POST request!
are two different things. We also have to get it to generate one and the same URL—/auctions
—but with a different HTTP request method, depending on the circumstances.
This is what the REST facility in Rails does for you. It lets you stipulate that you want /auctions
routed differently, depending on the HTTP request method. It lets you define named routes with the same name, but with intelligence about their HTTP verbs. In short, it uses HTTP verbs to provide that extra data slot necessary to achieve everything you want to achieve in a concise way.
The way you do this is by using a special form of routing command: map.resources
. Here’s what it would look like for auctions:
map.resources :auctions
That’s it. Making this one call inside routes.rb
is the equivalent of defining four named routes (as you’ll see shortly). And if you mix and match those four named routes with a variety of HTTP request methods, you end up with seven useful—very useful—permutations.
Calling map.resources :auctions
involves striking a kind of deal with the routing system. The system hands you four named routes. Between them, these four routes point to seven controller actions, depending on HTTP request method. In return, you agree to use very specific names for your controller actions: index, create, show, update, destroy, new, edit.
It’s not a bad bargain, since a lot of work is done for you and the action names you have to use are nicely CRUD-like.
Table 4.1 summarizes what happens. It’s a kind of “multiplication table” showing you what you get when you cross a given RESTful named route with a given HTTP request method. Each box (the nonempty ones, that is) shows you, first, the URL that the route generates and, second, the action that gets called when the route is recognized. (The table uses _url
rather than _path
, but you get both.)
Table 4.1. RESTful Routes Table Showing Helpers, Paths, and the Resulting Controller Action
Helper Method | GET | POST | PUT | DELETE |
---|---|---|---|---|
client_url(@client) | /clients/1 show | /clients/1 update | /clients/1 destroy | |
clients_url | /clients index | /clients create | ||
edit_client_url(@client) | /clients/1/edit edit | |||
new_client_url | /clients/new new |
(The edit
and new
actions have unique named routes, and their URLs have a special syntax. We’ll come back to these special cases a little later.)
Since named routes are now being crossed with HTTP request methods, you’ll need to know how to specify the request method when you generate a URL, so that your GET’d clients_url
and your POST’d clients_url
don’t trigger the same controller action. Most of what you have to do in this regard can be summed up in a few rules:
The default request method is GET.
In a form_tag
or form_for
call, the POST method will be used automatically.
When you need to (which is going to be mostly with PUT and DELETE operations), you can specify a request method along with the URL generated by the named route.
An example of needing to specify a DELETE operation is a situation when you want to trigger a destroy
action with a link:
<%= link_to "Delete this auction",:url => auction(@auction), :method => :delete %>
Depending on the helper method you’re using (as in the case of form_for
), you might have to put the method inside a nested hash:
<% form_for "auction", :url => auction(@auction), :html => { :method => :put } do |f| %>
That last example, which combined the singular named route with the PUT method, will result in a call to the update
action (as per row 2, column 4 of the table).
Web browsers generally don’t handle request methods other than GET and POST. Therefore, in order to send them PUT and DELETE requests, it’s necessary for Rails to do a little sleight of hand. It’s not anything you need to worry about, other than to be aware of what’s going on.
A PUT or DELETE request, in the context of REST in Rails, is actually a POST request with a hidden field called _method
set to either “put” or “delete”. The Rails application processing the request will pick up on this, and route the request appropriately to the update
or destroy
action.
You might say, then, that the REST support in Rails is ahead of its time. REST components using HTTP should understand all of the request methods. They don’t—so Rails forces the issue. As a developer trying to get the hang of how the named routes map to action names, you don’t have to worry about this little cheat. And hopefully some day it won’t be necessary any more.
Some of the RESTful routes are singular; some are plural. The logic is as follows.
The routes for show
, new
, edit
, and destroy
are singular, because they’re working on a particular resource.
The rest of the routes are plural. They deal with collections of related resources.
The singular RESTful routes require an argument, because they need to know the id of the particular member of the collection that you’re operating on. You can use either a straightforward argument-list syntax:
item_url(@item) # show, update, or destroy, depending on HTTP verb
or you can do it hash style:
item_url(:id => @item)
You don’t have to call the id
method on @item
(though you can), as Rails will figure out that that’s what you want.
As Table 4.1 shows, new
and edit
obey somewhat special RESTful naming conventions. The reason for this actually has to do with create
and update
, and how new
and edit
relate to them.
Typically, create
and update
operations involve submitting a form. That means that they really involve two actions—two requests—each:
The action that results in the display of the form
The action that processes the form input when the form is submitted
The way this plays out with RESTful routing is that the create
action is closely associated with a preliminary new
action, and update
is associated with edit
. These two actions, new
and edit
, are really assistant actions: All they’re supposed to do is show the user a form, as part of the process of creating or updating a resource.
Fitting these special two-part scenarios into the landscape of resources is a little tricky. A form for editing a resource is not, itself, really a resource. It’s more like a pre-resource. A form for creating a new resource is sort of a resource, if you assume that “being new”—that is, nonexistent—is something that a resource can do, and still be a resource...
Yes, it gets a bit philosophical. But here’s the bottom line, as implemented in RESTful Rails.
The new
action is understood to be giving you a new, single (as opposed to plural) resource. However, since the logical verb for this transaction is GET, and GETting a single resource is already spoken for by the show
action, new
needs a named route of its own.
That’s why you have to use
<%= link_to "Create a new item", new_item_path %>
to get a link to the items/new
action.
The edit
action is understood not to be giving you a full-fledged resource, exactly, but rather a kind of edit “flavor” of the show
resource. So it uses the same URL as show
, but with a kind of modifier, in the form of /edit
, hanging off the end, which is consistent with the URL form for new
:
/items/5/edit
It’s worth mentioning that prior to Rails 2.0, the edit
action was set off by semicolons like this: /items/5;edit
, a choice that may have had more to do with the limitations of the routing system than any other loftier motives. However, the semicolon scheme caused more problems than it solved,[2] and was scrapped in Edge Rails right after the release of Rails 1.2.3.
The corresponding named route is edit_item_url(@item)
. As with new
, the named route for edit
involves an extra bit of name information, to differentiate it from the implied show
of the existing RESTful route for GETting a single resource.
In addition to map.resources
, there’s also a singular (or “singleton”) form of resource routing: map.resource
. It’s used to represent a resource that only exists once in its given context.
A singleton resource route at the top level of your routes can be appropriate when there’s only one resource of its type for the whole application, or perhaps per user session.
For instance, an address book application might give each logged-in user an address book, so you could write:
map.resource :address_book
You would get a subset of the full complement of resource routes, namely the singular ones: GET/PUT address_book_url
, GET edit_address_book_url
, and PUT update_address_book_url
.
Note that the method name resource
, the argument to that method, and all the named routes are in the singular. It’s assumed that you’re in a context where it’s meaningful to speak of “the address book”—the one and only—because there’s a user to which the address book is scoped. The scoping itself is not automatic; you have to authenticate the user and retrieve the address book from (and/or save it to) the database explicitly. There’s no real “magic” or mind-reading here; it’s just an additional routing technique at your disposal if you need it.
Let’s say you want to perform operations on bids: create, edit, and so forth. You know that every bid is associated with a particular auction. That means that whenever you do anything to a bid, you’re really doing something to an auction/bid pair—or, to look at it another way, an auction/bid nest. Bids are at the bottom of a “drill-down” that always passes through an auction.
What you’re aiming for here is a URL that looks like this:
/auctions/3/bids/5
What it does depends on the HTTP verb it comes with, of course. But the semantics of the URL itself are: the resource that can be identified as bid 5, belonging to auction 3.
Why not just go for bids/5
and skip the auction? For a couple of reasons. First, the URL is more informative—longer, it’s true, but longer in the service of telling you something about the resource. Second, thanks to the way RESTful routes are engineered in Rails, this kind of URL gives you immediate access to the auction id, via params[:auction_id]
.
To created nested resource routes, put this in routes.rb
:
map.resources :auctions do |auction| auction.resources :bids end
Note that in the inner call to resources, the receiver of the call is auction
, not map
. That’s an easy thing to forget.
What that tells the mapper is that you want RESTful routes for auction resources; that is, you want auctions_url
, edit_auction_url
, and all the rest of it. You also want RESTful routes for bids: auction_bids_url
, new_auction_bid_url
, and so forth.
However, the nested resource command also involves you in making a promise: You’re promising that whenever you use the bid named routes, you will provide a auction resource in which they can be nested. In your application code, that translates into an argument to the named route method:
<%= link_to "See all bids", auction_bids_path(@auction) %>
When you make that call, you enable the routing system to add the /auctions/3
part before the /bids
part. And, on the receiving end—in this case, in the action bids/index
, which is where that URL points, you’ll find the id of @auction
in params[:auction_id]
. (It’s a plural RESTful route, using GET. See Table 4.1 again if you forgot.)
You can nest to any depth. Each level of nesting adds one to the number of arguments you have to supply to the nested routes. This means that for the singular routes (show
, edit
, destroy
), you need at least two arguments, as in Listing 4.1.
This will enable the routing system to get the information it needs (essentially @auction.id
and @bid.id
) in order to generate the route.
If you prefer, you can also make the same call using hash-style method arguments, but most people don’t because it’s longer code:
auction_bid_path(:auction => @auction, :bid => @bid)
You can also achieve the nested route effect by specifying the :path_prefix
option to your resource mapping call explicitly. Here’s how you’d do this for the auctions/bids nest:
map.resources :auctions map.resources :bids, :path_prefix => "auctions/:auction_id"
What you’re saying here is that you want all of the bids URLs to include the static string “auctions” and a value for auction_id
—in other words, to contain the contextual information necessary to associate the bid collection or member with a particular auction.
The main difference between this technique and regular nesting of resources has to do with the naming of the helper methods that are generated. Nested resources automatically get a name prefix corresponding to their parent resource. (See auction_bid_path
in Listing 4.1.)
You’re likely to see the nesting technique more often than the explicit setting of :path_prefix
, because it’s usually easier just to let the routing system figure it out from the way you’ve nested your resources. Plus, as we’ll see in a moment, it’s easy to get rid of the extra prefixes if you want to do so.
Sometimes you might want to nest a particular resource inside more than one other resource. Or you might want to access a resource through a nested route sometimes, and directly other times. You might even want your named route helpers to point to different resources depending on the context in which they are executed.[3] The :name_prefix
makes it all possible, since it lets you control the way that named route helper methods are generated.
Let’s say you want to get at bids through auctions, as in the preceding examples, but also just by themselves. In other words, you want to be able to recognize and generate both:
/auctions/2/bids/5 and /bids/5
The first thought might be bid_path(@auction, @bid)
for the first helper, and bid_path(@bid)
for the second. It seems logical to assume that if you want a route to bid that doesn’t pass through the auction nest, you’d just leave out the auction parameter.
Given the automatic name-prefixing behavior of the routing system, you’d have to override the name_prefix
of bids to make it all work as desired, as in Listing 4.2.
I will warn you, as someone who has used this technique extensively in real applications, that when you eliminate name prefixing, debugging route problems gets an order of magnitude harder. As they say, your mileage may vary.
As an example, what if we wanted a different way to access bids, via the person who made them, instead of in the context of auctions? See Listing 4.3.
Amazingly, the code in Listing 4.3 should[4] work just fine, and generate the following route helpers:
bid_path(@auction, @bid) # /auctions/1/bids/1 bid_path(@person, @bid) # /people/1/bids/1
The thing is that your controller and view code might start getting a wee bit complex if you go down this route (pardon the pun).
First of all, your controller code would have to check for the presence of params[:auction_id]
versus params[:person_id]
and load the context accordingly. The view templates would probably need to do similar checking, in order to display correctly. At worst your code would end up with tons of if/else
statements cluttering things up!
Whenever you’re programming dual functionality like that, you’re probably doing something wrong. Luckily, we can also specify which controller we would like to be involved in each of our routes explicitly.
Something we haven’t yet discussed is how RESTful routes are mapped to a given controller. It was just presented as something that happens automatically, which in fact it does, based on the name of the resource.
Going back to our recurring example, given the following nested route:
map.resources :auctions do |auction| auction.resources :bids end
...there are two controllers that come into play, the AuctionsController
and the BidsController
.
You could explicitly specify which controller to use with the :controller_name
option of the resources
method. Having the option means you can name the (user-facing) resource whatever you want, and keep the name of your controller aligned with different naming standards, for example:
map.resources :my_auctions, :controller => :auctions do |auction| auction.resources :my_bids, :controller => :bids end
Now that we know about the :name_prefix
, :path_prefix
, and :controller
options, we can bring it all together to show why having such fine-grained control over RESTful routes is useful.
For example, we can improve what we were trying to do in Listing 4.3, by using the :controller
option. See Listing 4.4.
Realistically, the AuctionBidsController
and PersonBidsController
would extend the same parent class BidsController
, as in Listing 4.5, and leverage before
filters to load things correctly.
Example 4.5. Subclassing Controllers for Use with Nested Routes
class BidsController < ApplicationController before_filter :load_parent before_filter :load_bid protected def load_parent # overriden in subclasses end def load_bid @bids = @parent.bids end end class AuctionBidsController < BidsController protected def load_parent @parent = @auction = Auction.find(params[:auction_id]) end end class PersonBidsController < BidsController protected def load_parent @parent = @person = Person.find(params[:person_id]) end end
Note that that although it is customary to provide name-style options as symbols, the :controller
option does understand strings, as you would need to use if you were specifying a namespaced controller, like this example, which sets up an administrative route for auctions:
map.resources :auctions, :controller => 'admin/auctions', # Admin::AuctionsController :name_prefix => 'admin_', :path_prefix => 'admin'
Is nesting worth it? For single routes, a nested route usually doesn’t tell you anything you wouldn’t be able to figure out anyway. After all, a bid belongs to an auction. That means you can access bid.auction_id
just as easily as you can params[:auction_id]
, assuming you have a bid object already.
Furthermore, the bid object doesn’t depend on the nesting. You’ll get params[:id]
set to 5, and you can dig that record out of the database directly. You don’t need to know what auction it belongs to.
Bid.find(params[:id])
A common rationale for judicious use of nested resources, and the one most often issued by David, is the ease with which you can enforce permissions and context-based constraints. Typically, a nested resource should only be accessible in the context of its parent resource, and it’s really easy to enforce that in your code based on the way that you load the nested resource using the parent’s ActiveRecord
association (see Listing 4.6).
If you want to add a bid to an auction, your nested resource URL would be:
http://localhost:3000/auctions/5/bids/new
The auction is identified in the URL rather than having to clutter your new bid form data with hidden fields, name your action add_bid
and stash the user in :id
, or any other non-RESTful practice.
Jamis Buck is a very influential figure in the Rails community, almost as much as David himself. In February 2007, via his blog,[5] he basically told us that deep nesting was a bad thing, and proposed the following rule of thumb: Resources should never be nested more than one level deep.
That advice is based on experience and concerns about practicality. The helper methods for routes nested more than two levels deep become long and unwieldy. It’s easy to make mistakes with them and hard to figure out what’s wrong when they don’t work as expected.
Assume that in our application example, bids have multiple comments. We could nest comments under bids in the routing like this:
map.resources :auctions do |auctions| auctions.resources :bids do |bids| bids.resources :comments end end
However, we’d have to start resorting to all sort of options to avoid having a auction_bid_comments_path
helper to deal with. (Actually, that’s not too bad, but I’ve seen and written much worse.)
Instead, Jamis would have us do the following:
map.resources :auctions do |auctions| auctions.resources :bids end map.resources :bids do |bids| bids.resources :comments end map.resources :comments
Notice that each resource (except auction) is defined twice, once in the top-level namespace, and one in its context. The rationale? When it comes to parent-child scope, you really only need two levels to work with. The resulting URLs are shorter, and the helper methods are easier to work with.
auctions_path # /auctions auctions_path(1) # /auctions/1 auction_bids_path(1) # /auctions/1/bids bid_path(2) # /bids/2 bid_comments_path(3) # /bids/3/comments comment_path(4) # /comments/4
I personally don’t follow Jamis’ guideline all the time in my projects, but I have noticed something about limiting the depth of your nested resources—it makes it all the more palatable to keep those handy-dandy name prefixes in place, instead of lopping them off with :name_prefix => nil
. And trust me, those name prefixes do help with maintainability of your codebase in the long run.
Rails’ RESTful routes give you a pretty nice package of named routes, hard-wired to call certain very useful and common controller actions—the CRUD superset you’ve already learned about. Sometimes, however, you want to customize things a little more, while still taking advantage of the RESTful route naming conventions and the “multiplication table” approach to mixing named routes and HTTP request methods.
The techniques for doing this are useful when, for example, you’ve got more than one way of viewing a resource that might be described as “showing.” You can’t (or shouldn’t) use the show
action itself for more than one such view. Instead, you need to think in terms of different perspectives on a resource, and create URLs for each one.
For example, let’s say we want to make it possible to retract a bid. The basic nested route for bids looks like this:
map.resources :auctions do |a| a.resources :bids end
We’d like to have a retract
action that shows a form (and perhaps does some screening for retractability). The retract
isn’t the same as destroy
; it’s more like a portal to destroy
. It’s similar to edit
, which serves as a form portal to update
.
Following the parallel with edit/update
, we want a URL that looks like this:
/auctions/3/bids/5/retract
and a helper method called retract_bid_url
. The way you achieve this is by specifying an extra :member
route for the bids
, as in Listing 4.7.:
Then you can add a retraction link to your view with the following code:
<%= link_to "Retract", retract_bid_path(auction, bid) %>
and the URL generated will include the /retract
modifier. That said, you should probably let that link pull up a retraction form (and not trigger the retraction process itself!). The reason I say that is because, according to the tenets of HTTP, GET requests should not modify the state of the server—that’s what POST requests are for.
Is it enough to add a :method
option to link_to
?
<%= link_to "Retract", retract_bid_path(auction,bid), :method=>:post
%>
Not quite. Remember that in Listing 4.7 we defined the retract route as a :get
, so a POST will not be recognized by the routing system. The solution is to define the extra member route as mapping to any HTTP verb, like this:
map.resources :auctions do |a|
a.resources :bids, :member => { :retract => :any }
end
You can also use this routing technique to add routes that conceptually apply to an entire collection of resources:
map.resources :auctions, :collection => { :terminate => :any }
This example will give you a terminate_auctions_path
method, which will produce a URL mapping to the terminate
action of the auctions controller. (A slightly bizarre example, perhaps, but the idea is that it would enable you to end all auctions at once.)
Thus you can fine-tune the routing behavior—even the RESTful routing behavior—of your application, so that you can arrange for special and specialized cases while still thinking in terms of resources.
During a discussion of RESTful routing on the Rails mailing list,[6] Josh Susser proposed flipping the syntax for custom actions so that they would be keyed on the HTTP verb and accept an array of action names, like this:
map.resources :comments, :member => { :get => :reply, :post => [:reply, :spawn, :split] }
Among other reasons, Josh cited how it would simplify the practice of writing so-called post-backs, dual-purpose controller actions that handle both GET and POST requests in one method.
The response from David was not a positive one. After expressing his position against post-backs, he said: “I’m starting to think that explicitly ignoring [post-backs] with map.resources
is a feature.”
Later in the thread, continuing to defend the API, David added, “If you’re writing so many additional methods that the repetition is beginning to bug you, you should revisit your intentions. You’re probably not being as RESTful as you could be.” (italics mine)
The last sentence is key. Adding extra actions corrupts the elegance of your overall RESTful application design, because it leads you away from finding all of the resources lurking in your domain.
Keeping in mind that real applications are more complicated than code examples in a reference book, let’s see what would happen if we had to model retractions strictly using resources. Rather than tacking a retract
action onto the BidsController
, we might feel compelled to introduce a retraction resource, associated with bids, and write a RetractionController
to handle it.
map.resources :bids do |bids| bids.resource :retraction end
RetractionController
could now be in charge of everything having to do with retraction activities, rather than having that functionality mixed into BidsController
. And if you think about it, something as weighty as bid retraction would eventually accumulate quite a bit of logic. Some would call breaking it out into its own controller proper separation of concerns or even just good object-orientation.
I can’t help but continue the story of that fateful mailing list thread, because it led to a priceless moment in Rails community history, which added to our reputation as an opinionated bunch!
Josh replied, “Just checking... You think that code that is less readable and more tedious to write is an advantage? I guess from the perspective of macro-optimization versus micro-optimization I wouldn’t argue with you, but I think that’s a hell of a way to encourage people to do the right thing. If going RESTful is all that, you shouldn’t need to rely on syntactic vinegar to force people to do it the right way. Now, if you were to say that organizing the actions hash as {:action => method, ...}
is desirable because it guarantees an action only is used once, then sure, that makes sense.” (italics mine)
David did indeed see the less readable and more tedious code as an advantage in this particular case, and he latched on to the syntactic vinegar term with enthusiasm. About two months later, he wrote one of his most famous blog entries about the concept (excerpted here):
Syntactic sugar has long hogged the spotlight in discussions of framework and language design. It holds the power to turn idioms into conventions, to promote a common style through beauty, brevity, and ease of use. We all love syntactic sugar—and we want it all: the terrifying lows, the dizzying highs, the creamy middles. It’s what makes languages such as Ruby taste ever so sweet in comparison to the plain alternatives.
But sugar is not all we need. Good design lies not only in emphasizing the proper, but de-emphasizing the improper too. Just as we can sprinkle syntactic sugar across a certain style or approach to promote its use, we can add syntactic vinegar to discourage it as well. It’s more sparingly used, but that makes it no less important. ... http://www.loudthinking.com/arc/2006_10.html
The word “resource” has a substantive, noun-like flavor that puts one in mind of database tables and records. However, a REST resource does not have to map directly to an ActiveRecord
model. Resources are high-level abstractions of what’s available through your web application. Database operations just happen to be one of the ways that you store and retrieve the data you need to generate representations of resources.
A REST resource doesn’t necessarily have to map directly to a controller, either, at least not in theory. As we learned in relation to the :path_prefix
and :controller
options of map.resources
, you could, if you wanted to, provide REST services whose public identifiers (URIs) did not match the names of your controllers at all.
What all of this adds up to is that you might have occasion to create a set of resource routes, and a matching controller, that don’t correspond to any model in your application at all. There’s nothing wrong with a full resource/controller/model stack where everything matches by name. But you may find cases where the resources you’re representing can be encapsulated in a controller but not a model.
An example in the auction application is the sessions controller. Assume a routes.rb
file containing this line:
map.resource :session
It maps the URL /session
to a SessionController
as a singleton resource, yet there’s no Session
model. (By the way, it’s properly defined as a singleton resource because from the user’s perspective there is only one session.)
Why go the RESTful style for authentication? If you think about it, user sessions can be created and destroyed. The creation of a session takes place when a user logs in; when the user logs out, the session is destroyed. The RESTful Rails practice of pairing a new
action and view with a create
action can be followed! The user login form can be the session-creating form, housed in the template file such as session/new.rhtml
(see Listing 4.8).
When the form is submitted, the input is handled by the create
method of the sessions controller in Listing 4.9.
Nothing is written to any database table in this action, but it’s worthy of the name create
by virtue of the fact that it creates a session. Furthermore, if you did at some point decide that sessions should be stored in the database, you’d already have a nicely abstracted handling layer.
It pays to remain open-minded, then, about the possibility that CRUD as an action-naming philosophy and CRUD as actual database operations may sometimes occur independently of each other—and the possibility that the resource-handling facilities in Rails might usefully be associated with a controller that has no corresponding model. Creating a session isn’t the most shining example of REST-compliant practices, since REST mandates stateless transfers of representations of resources... But it’s a good illustration of why, and how, you might make design decisions involving routes and resources that don’t implicate the whole application stack.
Sticking to CRUD-like action names is, in general, a good idea. As long as you’re doing lots of creating and destroying anyway, it’s easier to think of a user logging in as the creation of a session, than to come up with a whole new semantic category for it. Rather than the new concept of “user logs in,” just think of it as a new occurrence of the old concept, “session gets created.”
One of the precepts of REST is that the components in a REST-based system exchange representations of resources. The distinction between resources and their representations is vital.
As a client or consumer of REST services, you don’t actually retrieve a resource from a server; you retrieve representations of that resource. You also provide representations: A form submission, for example, sends the server a representation of a resource, together with a request—for example, PUT—that this representation be used as the basis for updating the resource. Representations are the exchange currency of resource management.
The ability to return different representations in RESTful Rails practice is based on the respond_to
method in the controller, which, as you’ve seen, allows you to return different responses depending on what the client wants. Moreover, when you create resource routes you automatically get URL recognition for URLs ending with a dot and a :format
parameter.
For example, assume that you have map.resources :auctions
in your routes file and some respond_to
logic in the AuctionsController
like this:
def index @auctions = Auction.find(:all) respond_to do |format| format.html format.xml { render :xml => @auctions.to_xml } end end
Now, you’ll now be able to connect to this URL: http://localhost:3000/auctions.xml
The resource routing will ensure that the index
action gets executed. It will also recognize the .xml
at the end of the route and interact with respond_to
accordingly, returning the XML representation.
Of course, all of this is URL recognition. What if you want to generate a URL ending in .xml
?
The resource routing facility also gives you .:format
-flavored versions of its named routes. Let’s say you want a link to the XML representation of a resource. You can achieve this by using the formatted_
version of the RESTful named route:
<%= link_to "XML version of this auction",
formatted_auction_path(@auction, "xml") %>
This will generate the following HTML:
<a href="/auctions/1.xml">XML version of this auction</a>
When followed, this link will trigger the XML clause of the respond_to
block in the show
action of the auctions controller. The resulting XML may not look like much in a browser, but the named route is there if you want it.
The circuit is now complete: You can generate URLs that point to a specific response type, and you can honor requests for different types by using respond_to
. And if the request wants to specify its desired response by using the Accept
header instead, it can do that too. All told, the routing system and the resource routing facilities built on top of it give you quite a set of powerful, concise tools for differentiating among requests and, therefore, being able to serve up different representations.
Rails’ REST facilities, ultimately, are about named routes and the controller actions to which they point. The more you use RESTful Rails, the more you get to know each of the seven RESTful actions. How they work across different controllers (and different applications) is of course somewhat different. Still, perhaps because there’s a finite number of them and their roles are fairly well-delineated, each of the seven tends to have fairly consistent properties and a characteristic “feel” to it.
We’re going to take a look at each of the seven actions, with examples and comments. You’ve encountered all of them already, particularly in Chapter 2, “Working with Controllers,” but here you’ll get some “backstory” and start to get a sense of the characteristic usage of them and issues and choices associated with them.
Typically, an index
action provides a representation of a plural (or collection) resource. The index representation will usually be generic and public. The index
action shows the world the most neutral representation possible.
A typical index
action looks like this:
class AuctionsController < ApplicationController def index @auctions = Auction.find(:all) end ... end
The view template will display public information about each auction, with links to specific information about each one, and to public profiles of the sellers.
Although index
is best thought of as public, you’ll certainly encounter situations where you want to display a representation of a collection, but in a restricted way. For example, users should be able to see a listing of all their bids. But you don’t want everyone seeing everyone else’s lists.
The best strategy here is to slam down the gate at the latest possible point. You can use RESTful routing to help you.
Let’s say we want each user to see his or her bid history. We could decide that the index
action of the bids controller will be filtered through the current user (@user
). The problem with that, though, is that it rules out a more public use of that action. What if we want a public collection view that shows all the current highest bids? Maybe even a redirect to the auction index view. The point is to keep things as public as possible for as long as possible.
There are a couple of ways to do this. One way is to test for the presence of a logged-in user, and decide what to show based on that. But that’s not going to work here. For one thing, the logged-in user might want to see the more public view. For another, the more dependence on server-side state we can eliminate or consolidate, the better.
So let’s look at the two bid lists, not as a public and private version of the same resource, but as different resources. We can encapsulate the difference directly in the routing:
map.resources :auctions do |auctions| auctions.resources :bids, :collection => { :manage => :get } end
We can now organize the bids controller in such a way that access is nicely layered, using filters only where necessary and eliminating conditional branching in the actions themselves:
class BidsController < ApplicationController before_filter :load_auction before_filter :check_authorization, :only => :manage def index @bids = Bid.find(:all) end def manage @bids = @auction.bids end ... protected def load_auction @auction = Auction.find(params[:auction_id]) end def check_authorization @auction.authorized?(current_user) end end
There’s now a clear distinction between /bids
and /bids/manage
and the role that they play in your application.
On the named route side, we’ve now got bids_url
and manage_bids_url
. We’ve thus preserved the public, stateless face of the /bids
resource, and quarantined as much stateful behavior as possible into a discrete subresource, /bids/manage
. Don’t fret if this mentality doesn’t come to you naturally—it’s part of the REST learning curve.
If I were dogmatic about REST, I might find it ironic, even distasteful, to discuss REST-related techniques in the context of quarantining stateful behavior, since RESTful requests are not supposed to depend on session state to begin with. It goes to show, however, that the REST facilities available to you in Rails can, so to speak, gracefully degrade, in situations where you need to depart from a strictly REST-compliant interface.
The RESTful show
action is the singular flavor of a resource. That generally translates to a representation of information about one object, one member of a collection. Like index
, show
is triggered by a GET request.
A typical—one might say classic—show
action looks like this:
class AuctionController < ApplicationController def show @auction = Auction.find(params[:id]) end end
Of course, the show
action might depend on before_filters
as a way of not having to load the shown resource explicitly in the show
action. You might want to differentiate between publicly available profiles, perhaps based on a different route, and the profile of the current user, which might include modification rights and perhaps different information.
As with index actions, it’s good to make your show actions as public as possible, and offload the administrative and privileged views into either a different controller or a different action.
Destroy actions are good candidates for administrative safeguarding, though of course it depends on what you’re destroying. You might want something like the code in Listing 4.10 to protect the destroy
action.
A typical destroy
action might look like this, assuming that @user
was already loaded by a before
filter:
def destroy @user.destroy flash[:notice] = "User deleted!" redirect_to users_url end
This approach might be reflected in a simple administrative interface like this:
<h1>Users</h1> <% @users.each do |user| %> <p><%= link_to h(user.whole_name), user_path(user) %> <%= link_to("delete", user_path(user), :method => :delete) if current_user.admin? %></p> <% end %>
That delete link appears, depending on the whether current user is an admin.
In fact, the most striking thing about the RESTful destroy sequence in Rails is what happens in the view that contains the links to the action. Here’s the HTML from one time through the loop. Be warned: It’s longer than you might think.
<p><a href="http://localhost:3000/users/2">Emma Knight Peel</a> <a href="http://localhost:3000/users/2" onclick="var f = document.createElement('form'), f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'), m.setAttribute('type', 'hidden'), m.setAttribute('name', '_method'), m.setAttribute('value', 'delete'), f.appendChild(m);f.submit();return false;">Delete</a>)</p>
Why so much code—JavaScript, yet!—for two little links? The first link is handled quickly; it’s just a link to the show view for the user. The reason the second link is so long is this. DELETE
submissions are dangerous. Rails wants to make them as hard as possible to spoof or trigger accidentally—for instance, by a crawler or bot sending requests to your site. So when you specify the DELETE
method, a whole JavaScript script is generated inside your HTML document. This script actually wraps your link in a form. Since bots don’t submit forms, this gives a layer of protection to your code.
As you’ve already seen, the new
and create
actions go together in RESTful Rails. A “new resource” is really just a virtual entity waiting to be created. Accordingly, the new
action customarily presents a form, and create
creates a new record, based on the form input.
Let’s say you want a user to be able to create (that is, start) an auction. You’re going to need
A new
action, which will display a form
A create
action, which will create a new Auction
object based on the form input, and proceed to a view (show
action) of that auction.
The new
action doesn’t have to do much. In fact, it has to do nothing. Like any empty action, it can even be left out. Rails will still figure out that you want to render the new.erb.html
view.
The new.erb.html
template might look like Listing 4.11. Notice that some of the input fields are namespaced to :item
(courtesy of the fields_for
helper method) and some are namespaced to :auction
(courtesy of form_for
). That’s because an item and an auction really get created in tandem.
Example 4.11. A New Auction Form
<h1>Create a new auction</h1> <%= error_messages_for :auction %> <% form_for :auction, :url => auctions_path do |f| %> <% fields_for :item do |i| %> <p>Item description: <%= i.text_field "description" %></p> <p>Item maker: <%= i.text_field "maker" %></p> <p>Item medium: <%= i.text_field "medium" %></p> <p>Item year: <%= i.text_field "year" %></p> <% end %> <p>Reserve: <%= f.text_field "reserve" %></p> <p>Bid increment: <%= f.text_field "incr" %></p> <p>Starting bid: <%= f.text_field "starting_bid" %></p> <p>End time: <%= f.datetime_select "end_time" %> <%= submit_tag "Create" %> <% end %>
The form action here is expressed by the named route auctions, coupled with the fact that this is a form and will therefore automatically generate a POST request.
Once the information is filled out, it’s time for the main event: the create
action. Unlike new
, this action has something to do.
def create @auction = current_user.auctions.build(params[:auction]) @item = @auction.build_item(params[:item]) if @auction.save flash[:notice] = "Auction started!" redirect_to auction_url(@auction) else render :action => "new" end end
Having used both an "auction"
namespace and an "item"
namespace for our input fields, we can piggyback on both, via the params
hash, to instantiate a new Auction
object from the current user’s auctions association and hang an Item
object off it with build_item
. This is a convenient way to operate on two associated objects at once. If @auction.save
fails for any reason, the associated item will not be created, so we don’t have to worry about cleaning up after a failure.
When the save succeeds, both auction and item will be created.
Like new
and create
, the edit
and update
actions go together: edit
provides a form, and update
processes the form input.
The form for editing a record is very similar to the form for creating one. (In fact, you can put much of it in a partial template and use it for both; that’s left as an exercise for the reader.) Here’s what edit.html.erb
might look like for editing an item:
<h1>Edit Item</h1> <% form_for :item, :url => item_path(@item), :html => { :method => :put } do |item| %> <p>Description: <%= item.text_field "description" %></p> <p>Maker: <%= item.text_field "maker" %></p> <p>Medium: <%= item.text_field "medium" %></p> <p>Year: <%= item.text_field "year" %></p> <p><%= submit_tag "Save Changes" %></p> <% end %>
The main difference between this form and the form for specifying a new item (Listing 4.11) is which named RESTful route you use, and the fact that for the update
action, you have to specify the PUT request method. That will have the effect of steering the dispatcher to the update method.
In this chapter, we tackled the tough subject of using REST principles to guide the design of our Rails applications, mainly as they apply to the routing system and controller actions. We learned how the foundation of RESTful Rails is the map.resources
method in your routes file, and how to use the numerous options available to make sure that you can structure your application exactly how it needs to be structured. We also learned how in some cases, David and Rails core team apply syntactic vinegar to keep us from straying down the wrong path.
One of the challenges of writing and maintaining serious Rails applications is the routing system, namely understanding it and being able to figure out mistakes that you will undoubtedly make during the course of day-to-day development. It’s such a crucial topic for the Rails developer that we have an entire chapter devoted to it.
1. | For those interested in REST, the canonical text is Roy Fielding’s dissertation, which you can find at http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. In particular, you’ll probably want to focus on Chapters 5 and 6 of the dissertation, which cover REST and its relation to HTTP. You’ll also find an enormous amount of information, and links to more, on the REST wiki at http://rest.blueoxen.net/cgi-bin/wiki.pl. |
2. | In addition to being weird, the semicolon had a number of significant problems. For instance, it wreaked havoc on caching. Safari users were not able to authenticate URLs with semicolons in them. Also, various web servers (most damningly Mongrel) correctly consider the semicolon to be part of the query string, since that character is reserved for delimiting the start of path parameters (specific to a path element in between slashes, as opposed to request parameters that come after a ‘?’ character). |
3. | Trevor Squires has a great plugin called ResourceFu that makes this technique possible, which is available at http://agilewebdevelopment.com/plugins/resource_fu. |
4. | I can only say should work, because routing code is historically some of the most volatile in the entire Rails codebase, so whether it works depends on your version of Rails. I know for a fact that it doesn’t work in Rails 1.2.3. |
5. | |
6. | Read the full thread at http://www.ruby-forum.com/topic/75356. |
18.117.138.104