3.12 The RESTful Rails Action Set
3.12.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.
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 maybe 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 action callbacks
only where necessary and eliminating conditional branching in the actions themselves:
class BidsController < ApplicationController
before_action :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.12.2 Show
The RESTful show action is the singular flavor of a resource. That generally translates to a representation ofinformation about one object, one member of a collection.
class AuctionController < ApplicationController
def show
@auction = Auction.find(params[:id])
end
end
3.12.3 Destroy
Destroy actions are good candidates for administrative safeguarding, though of course it depends on whatyou’re destroying. You might want something like this to protect the destroy action.
class ProductsController < ApplicationController
before_action :admin_required, only: :destroy
def destroy
product.destroy
redirect_to products_url, notice: "Product deleted!"
end
This approach might be reflected in a simple administrative interface like
%h1 Products
- products.each do |product|
%p= link_to product.name, product
- if current_user.admin?
%p= link_to "delete", product, method: :delete
The Rails 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.
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 theDELETE
method, JavaScript that submits a form is bound to your “delete” link, along with arel="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.12.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.
protected
def auction
@auction ||= current_user.auctions.build(params[:auction])
end
helper_method:auction
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 :new
end
end
3.12.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 PATCH
method on the form.