- 3.1 REST in a Rather Small Nutshell
- 3.2 Resources and Representations
- 3.3 REST in Rails
- 3.4 Routing and CRUD
- 3.5 The Standard RESTful Controller Actions
- 3.6 Singular Resource Routes
- 3.7 Nested Resources
- 3.8 RESTful Route Customizations
- 3.9 Controller-Only Resources
- 3.10 Different Representations of Resources
- 3.11 The RESTful Rails Action Set
- 3.12 Conclusion
3.11 The RESTful Rails Action Set
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'll encounter all of them again, particularly in Chapter 4, 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.
3.11.1 Index
Typically, an index action provides a representation of a plural (or collection) resource. However, to be clear, not all resource collections are mapped to the index action. Your default index representations will usually be generic, although admittedly that has a lot to do with your application-specific needs. But in general, the index action shows the world the most neutral representation possible. A very basic index action looks like
class AuctionsController < ApplicationController def index @auctions = Auction.all end end
The associated view template will display information about each auction, with links to specific information about each one, and to profiles of the sellers.
You'll certainly encounter situations where you want to display a representation of a collection in a restricted way. In our recurring example, users should be able to see a listing of all their bids, but may be you don't want users seeing other people's bids.
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 try looking at the two bid lists, not as public and private versions of the same resource, but as different index resources. The difference can be reflected in the routing like:
resources :auctions do resources :bids do get :manage, :on => :collection end end resources :bids
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 :check_authorization, :only => :manage def index @bids = Bid.all end def manage @bids = auction.bids end protected def 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 /auctions/1/bids/manage and the role that they play in your application.
On the named route side, we've now got bids_url and manage_auction_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 member resource, /auctions/1/bids/manage. Don't fret if this mentality doesn't come to you naturally. It's part of the REST learning curve.
3.11.2 Show
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
class AuctionController < ApplicationController def show @auction = Auction.find(params[:id]) end end
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 onto either a different controller or a different action.
3.11.3 Destroy
Destroy actions are good candidates for administrative safeguarding, though of course it depends on what you're destroying. You might want something like this to protect the destroy action.
class ProductsController < ApplicationController before_filter :admin_required, :only => :destroy
A typical destroy action might look like
def destroy product.destroy flash[:notice] = "Product deleted!" redirect_to products_url end
This approach might be reflected in a simple administrative interface like
%h1 Products - products.each do |p| %p= link_to p.name, product_path(p) - if current_user.admin? %p= link_to "delete", product_path(p), :method => :delete
That delete link appears depending on whether current user is an admin.
With Rails 3, the UJS (Unobtrusive JavaScript) API greatly simplifies the HTML emitted for a destroy action, using CSS selectors to bind JavaScript to (in this case) the "delete" link. See Chapter 12, Ajax on Rails, for much more information about how it works.
DELETE submissions are dangerous. Rails wants to make them as hard as possible to trigger accidentally—for instance, by a crawler or bot sending requests to your site. So when you specify the DELETE method, JavaScript that submits a form is bound to your "delete" link, along with a rel="nofollow" attribute on the link. Since bots don't submit forms (and shouldn't follow links marked "nofollow"), this gives a layer of protection to your code.
3.11.4 New and Create
As you've already seen, the new and create actions go together in RESTful Rails. A "new resource" is really just an 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 which view to render. However, your controller will need an auction helper method, like
protected def auction @auction ||= current_user.auctions.build(params[:auction]) end helper_method :auction
If this technique is alien to you, don't worry. We'll describe it in detail in Section 10.1.5.
A simplistic new.html.haml template might look like Listing 3.2.
Listing 3.2. A New Auction Form
%h1 Create a new auction = form_for auction do |f| = f.label :subject = f.text_field :subject %br = f.label :description = f.text_field :description %br = f.label :reserve = f.text_field :reserve %br = f.label :starting_bid = f.text_field :starting_bid %br = f.label :end_time = f.datetime_select :end_time %br = f.submit "Create"
Once the information is filled out by a user, it's time for the main event: the create action. Unlike new, this action has something to do.
def create if auction.save redirect_to auction_url(auction), :notice => "Auction created!" else render :action => "new" end end
3.11.5 Edit and Update
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 appears 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.)
The form_for method is smart enough to check whether the object you pass to it has been persisted or not. If it has, then it recognizes that you are doing an edit and specifies a PUT method on the form.