Problem
You’ve heard all the buzz about creating RESTful this and that. There’s
little doubt that Rails 2.0 heavily favors the REST architectural style1
and that it will continue to do so.
You’re feeling a little out of the loop, and what you’ve heard so far is
a tad too academic. As a practical matter, you’d like to create a web-
accessible API for one of your models and write a client program to
administer that model remotely. What can resources do for you?
Solution
Let’s forget about REST for a moment and try to solve a problem. Sup-
pose we organize events and we want a web-based interface for creating,
reading, updating, and deleting events (affectionately known as CRUD).
The first part of the solution comes in a single command, but what it
does can be confusing. The scaffold generator churns out all the CRUD
support code for administering events in a RESTful way.2 We’ll put up
event scaffolding in one fell swoop:
$ script/generate scaffold event name:string description:text ֓
capacity:integer price:decimal starts_at:datetime
That single command generates a bunch of code: a model, a controller
with no fewer than seven actions, view template files for actions that
need them, tests, and even a migration file with the database columns
we listed on the command line.
Next, we just need to create the database and apply the migration:
$ rake db:create
$ rake db:migrate
That’s all there is to it! Fire up the application, and, lo and behold, we
now have a full HTML interface to CRUD (the verb form) events.
Before we move on to the second part of the solution, let’s dive a bit
deeper to see what just happened. In some ways, it’s just like the old
Rails scaffolding, but there’s a significant twist. You may have noticed
that the following line was added to our config/routes.rb file:
map.resources :events
That line is the key ingredient to our RESTful application. It dynam-
ically creates a set of named RESTful routes for accessing our events
(called resources) via URLs. The routes map to the seven actions in our
controller: index, show, new, create, edit, update, and destroy.
The best way to see what’s going on behind the scenes is to print out
all the defined routes by typing this:
$ rake routes
Let’s look at a few just to get a taste of how this works (you’ll see more in
your output). First, we have routes for dealing with the events collection:
events GET /events {:controller=>"events", :action=>"index"}
POST /events {:controller=>"events", :action=>"create"}
The leftmost column gives the name of the route (events), followed by
the matching HTTP verb and URL path, and then the action/controller
pair that the route ends up invoking. So, to list all our events—the
index action—we would issue an HTTP GET request to the URI /events.
Then, inside our application, we use the events_url route helper to gen-
erate the full URL for listing events. (Alternatively, you can use the
events_path method to just get the path part of the URL, often referred
to as the URI.)
Notice that the same incoming URL path is mapped to our create action.
The only difference is the HTTP verb that’s used: GET is a read-only
operation that lists the events, and POST is a write operation that cre-
ates a new event.
We also have RESTful routes for dealing with a specific member of the
events collection:
event GET /events/:id {:controller=>"events", :action=>"show"}
PUT /events/:id {:controller=>"events", :action=>"update"}
In these cases, the name of the route is singular (event) because it’s
dealing with one particular event. Again, the URL path is the same for
both actions. The HTTP verb is used to disambiguate whether we want
to read or update a single event by its primary key (the :id route param-
eter). Inside our application, we use the event_url(@event) route helper,
for example, to generate a URL to show our event. Passing @event to
the route helper will automatically fill in the :id route parameter with
the ID of the event.
So, in a nutshell, the map.resources line generates RESTful routes into
our application based on both the incoming URL path and an HTTP
verb. Now, browsers generally issue GET and POST requests, leaving
the other HTTP verbs to the academics. So, how do we tell our appli-
cation that we want to update an existing event (the PUT verb) or to
delete an event (the DELETE verb)? Well, that involves a few more new
conventions.
If we have an @event model object (and it’s declared to be a resource),
then in our new.html.erb and edit.html.erb forms, we can simply use this:
Download Rest/app/views/events/new.html.erb
<% form_for @event do |f| -%>
The form_for will generate the appropriate form tag depending on the
state of the @event object. If it’s a new record that hasn’t yet been saved,
Rails knows that it needs to be created. So, the form_for generates a form
tag that looks like this:
<form action="/events" method="post" >
This form will post to our create action because, according to the REST-
ful routing conventions, the HTTP verb and URL path map to that
action.
However, if the event is an existing record that has previously been
saved, then Rails knows we’re trying to update it. In this case, the form
should post to the update action. To do that, the form_for method slaps
in a hidden field to simulate a PUT operation. This in turn triggers
the proper RESTful route when Rails intercepts the request. It looks
something like this in the generated form:
<form action="/events/1" method="post" >
<input name="_method" type="hidden" value="put" />
OK, so at the end of all this, we’re still administering events in the
browser, just with special URLs. The immediate upshot is we can turn
any of our models into resources—events, registrations, users, and so
on—and then administer them through a consistent URL scheme. It’s
just one more example of Rails conventions removing guesswork.
Now let’s move on to the second part of the solution. Let’s say we’d like
to write a client program to administer events remotely. We’ll have our
client program speak XML back and forth with our Rails application
(imagine what that must sound like!). To do that, let’s turn our atten-
tion to the scaffold-generated index action in our EventsController. It has
all the familiar stuff, plus a respond_to block:
Download Rest/app/controllers/events_controller.rb
def index
@events = Event.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @events }
end
end
The action starts by setting up a collection of events. The respond_to
block determines how those events are rendered based on which for-
mat the client requests. By default, browsers prefer the HTML format.
In that case, the index action renders the index.html.erb template to gen-
erate an HTML response.
However, the index action also responds to requests for events in an
XML format. To get the collection of events as XML, we just need our
client program to issue a GET request with .xml tacked to the end of the
URL, like this:
http://localhost:3000/events.xml
Here’s where things get interesting. We already have a consistent way
to administer our events using the RESTful URL conventions. And we
already have a way to vary how the events are represented using the
respond_to block in actions.
Therefore, we already have a web-accessible API to our event resources.
All we need to do is write a client program to administer our events
remotely. Active Resource makes that really easy.
First, we write a stand-alone Ruby program: the Active Resource client.
(I typically put these in a services directory, but they could live any-
where.) It doesn’t need to load Rails per se, but we do need to require
the activeresource gem:
Download Rest/services/event_client.rb
require 'rubygems'
require 'activeresource'
Then we create an Event proxy class that points to the server where the
event resources live:
Download Rest/services/event_client.rb
class Event < ActiveResource::Base
self.site = "http://localhost:3000"
end
Active Resource now knows how to build URLs for accessing our event
resources, and it’ll push XML over HTTP until the cows come home.
Indeed, here’s where having an application that responds to XML really
shines. All the standard CRUD-level operations are available, as if our
proxy class were a real Active Record model.
Next, we’ll find all the events and print their names:
Download Rest/services/event_client.rb
events = Event.find(:all)
puts events.map(&:name)
Then we’ll find a specific event and update its attributes:
Download Rest/services/event_client.rb
e = Event.find(1)
e.price = 20.00
e.save
Finally, we’ll create and delete an event:
Download Rest/services/event_client.rb
e = Event.create(:name => "Shortest event evar!" ,
:starts_at => 1.second.ago,
:capacity => 25,
:price => 10.00)
e.destroy
Before we run the client, we’ll configure a logger to see what happened
behind the scenes by adding this line before the Event class definition:
Download Rest/services/event_client.rb
ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/events.log" )
Then, to run the client, we just use this:
$ ruby event_client.rb
Here’s the output in the events.log file, which can be quite handy for
debugging an ornery client:
GET http://localhost:3000/events.xml
--> 200 OK (<?xml version="1.0" encoding="UTF-8"?> ...
GET http://localhost:3000/events/1.xml
--> 200 OK (<?xml version="1.0" encoding="UTF-8"?> ...
PUT http://localhost:3000/events/1.xml
--> 200 OK ( b 0.11s)
POST http://localhost:3000/events.xml
--> 201 Created (<?xml version="1.0" encoding="UTF-8"?> ...
DELETE http://localhost:3000/events/12.xml
--> 200 OK ( b 0.11s)
Now we have a full API for creating, reading, updating, and deleting
events via the browser or a remote client program. And we have conven-
tions: a consistent set of URLs that map to a consistent set of actions.
This generally makes deciding where things go a lot easier. In particular,
we no longer have controllers that are dumping grounds for spurious
actions.
Discussion
It is important to note here that REST (map.resources in particular)
and respond_to blocks are orthogonal. You can use respond_to without
resources, and vice versa.
Although Active Resource ships with Rails and uses some of its support
classes, it’s not necessarily Rails-specific. You can use it to reach out
to any server supporting the RESTful conventions of Rails.
Also See
The map.resources method generates routes only for the seven CRUD
actions. This begs the question, how do I call actions outside of this
set? Recipe 2, Add Your Own RESTful Actions, on the following page
shows how to customize the routes for application-specific concerns.