Home > Articles > Programming > Ruby

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

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

  1. A new action, which will display a form
  2. 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.

  • + Share This
  • 🔖 Save To Your Account