REST and Ruby on Rails: The Big Picture

REST is quickly gaining ground as a way to make web apps talk to each other. But implementing it in Rails can be confusing for beginners, due in no small part to the rather lacking documentation of the big picture. In this article, I explain how ActiveResource, routing, and RESTful controller design fit together.

What is REST?

Wikipedia will give you the computer science definition, but for mortals like us, REST is best understood as a standardized way of using HTTP to facilitate machine-to-machine communication. For example, suppose you have an online classifieds app like Craigslist, and you want to partner with local newspapers to share ads (you display theirs, they display yours). You'd need a way for your servers to tell their servers about new postings, and vice-versa. REST is a way of making this happen.

Client-server model

REST is based on a client-server model. Note that both the client and server in a RESTful communication may actually be webservers (as in the example above). Nonetheless, it's still considered a client-server model because one machine (the client) sends a request to the other machine (the server), which then returns a response. In some cases, the each machine may act in both capacities. For example, in the aformentioned classifieds system, Craigslist may at times be the one receiving updates from the local paper (in which case it's the server), and at other times sending updates to the local paper (in which case it's the client).

If you're having trouble keeping track of which is which, here's a simple rule: the client is the one that sends the request, and the server is the one that returns the response.

HTTP verbs

RESTful communications consist of HTTP request-response cycles. In the Craigslist example, when the local paper receives a new ad from one of its readers, three things happen:

  1. The local paper sends a POST request to Craigslist containing the title and text of the ad.
  2. Craigslist saves the ad to its database and displays it amidst the other listings.
  3. Craigslist returns an HTTP response saying that the ad was successfully received.

But not every RESTful communication is a POST request. REST takes advantage of all four HTTP verbs: GET, POST, and the little-known PUT and DELETE. Each has a different meaning in the context of a REST request.

  • GET asks the server to return some data without changing anything on the server.
  • POST asks the server to create a new record. For example, POSTing to a RESTful Craigslist would create a new ad.
  • PUT asks the server to change an existing record. For example, if your ad has a typo, you can send Craigslist a PUT request with the correction.
  • DELETE asks the server to delete a record. If you change your mind and don't want to sell your accordion anymore, you'll want to send Craigslist a DELETE request to remove your ad.

Note that the server can refuse to do what the client requests. In that case, the server should say why.

These verbs are known as HTTP methods.

URLs and Resources

Each of these HTTP methods operates on something. In our example, that something is the collection of all ads on the server. But Craigslist might expose more than one RESTful service. For example, it may also need to communicate with a payroll app provided by a vendor. In that case, there would have to be a way for the server to distinguish between an ad being POSTed and an employee record being POSTed. Enter the URL, and the concept of resources.

In this situation, Craigslist would provide two URLs:

  • craigslist.com/ads
  • craigslist.com/employees

For example, affiliates would POST to craigslist.com/ads to create a new add, and the payroll app would POST to craigslist.com/employees to create new employees. The same applies to the other three HTTP methods. In some cases, the client wants to view or modify a particular ad or employee, in which case the ID is appended to the URLs. Let's see some more examples.

  • GET craigslist.com/ads returns a list of all the current ads.
  • DELETE craigslist.com/ads/12 deletes the ad with ID 12.
  • GET craigslist.com/employees/4 returns the information for the employee with ID 4.
  • PUT craigslist.com/employees/4 updates the information for the employee with ID 4. In this case, the updated data (such as a change to the employee's contact info) would be encoded in the body of the request.

URL stands for Uniform Resource Locator. That might not make much sense in the context of a basic link in an HTML document, but with REST, its meaning is much more clear. The URLs craigslist.com/ads and craigslist.com/employees identify two resources: the collection of all ads, and the collection of all employees.

In the context of Rails, when we talk about resources, this is exactly what we mean. A resource is a collection of objects or a single object, identified by a URL, that can be accessed and manipulated with GET, POST, PUT, and DELETE.

The Rails implementation

REST on Rails involves five components:

  • Routing
  • RESTful controller design
  • respond_to blocks and HTTP responses
  • RESTful forms
  • ActiveResource

I'll explain each in turn.

Routing

In the default configuration, Rails uses URLs of the form example.com/controller/action. Things are a bit different with REST.

With REST, the HTTP methods each need to invoke a different action for the same URL. For example, POST craigslist.com/ads would invoke AdsController#create, while GET craigslist.com/ads would invoke AdsController#index. The URL is the same in both cases; it's just the HTTP method that makes the difference. This isn't possible with old-style Rails URLs of the form craigslist.com/controller/action.

So how do we map combinations of URLs and HTTP methods to actions? Luckily, it's drop-dead easy. For our Craiglist example, we'd go into the main block of routes.rb and add two lines:

1
2
map.resources :ads
map.resources :employees

From these simple lines, Rails will automatically generate a bunch of URL/method mappings. Rather than enumerate them here, I'll refer you to the ActionController::Resources documentation. You can do a lot more with resources in routes.rb than I've explained here, but map.resources is all you'll need for now.

If you've read the docs, you may have noticed the named routes that map.resources gives you. These, like any other named routes, can be used in both controllers and views. Their use is optional—you can still do things like :controller => 'employees', :action => 'show', :id => @employee.id—but I find the newer, named routes a bit more convenient and readable. For example:

<%= link_to 'Create new employee', new_employee_url %>

RESTful controller design

Each resource has exactly one corresponding controller. The controller must have the same name as the resource. For example, the employees resource would have a controller like this:

1
2
3
class EmployeesController < ActionController::Base
        # ...
end

In a non-RESTful Rails app, you can call your actions whatever you want. But with REST, you have to follow some naming conventions. If you don't, map.resources won't work; it expects actions with certain names. The ActionController::Resources documentation lists these action names. In brief, they are: new, create, show, edit, update, and destroy.

In the case of show, edit, update, and destroy, params[:id] will be set. (It tells you which record to show, edit, update, or destroy.)

Just a reminder: the action names do not directly correspond to the URLs. For example, GET employees/42 would invoke EmployeesController#show and set params[:id] = 42. If you get confused about what URL to use, just look at the ActionController::Resources documentation and pick the appropriate named route.

The RESTful actions divide up the functionality in ways you might not be used to. For example, you may have done something like this before:

1
2
3
4
5
6
7
8
class EmployeesController < ActionController::Base
        def edit
                @employee = params.has_key?(:id) ? Employee.find(params[:id]) : Employee.new
                if request.post? and @employee.update_attributes(params[:employee])
                        redirect_to :action => 'show', :id => @employee.id
                end
        end
end

In the example above, several steps are rolled into one action: displaying the form, creating a new record, and updating an existing record. But with REST, you have to split these steps up into their own actions. The above edit action would be broken into four separate ones: new, create, edit, and update, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class EmployeesController < ActionController::Base
        def new
                @employee = Employee.new
                render :action => 'edit'
        end
        
        def create
                @employee = Employee.new
                if @employee.update_attributes(params[:employee])
                        # Notice the use of the named route
                        redirect_to employee_url(@employee)
                else
                        # The @employee object, complete with
                        # error messages, will be passed into
                        # the view.
                        render :action => 'edit'
                end
        end
        
        def edit
                @employee = Employee.find(params[:id])
        end
        
        def update
                @employee = Employee.find(params[:id])
                if @employee.update_attributes(params[:employee])
                        redirect_to employee_url(@employee)
                else
                        render :action => 'edit'
                end
        end
end

respond_to blocks

In the old days, a website was thought of as a collection of HTML pages. With REST, on the other hand, the Web is thought of as a bunch of interconnected resources, such as Craigslist's ad listings and employee rosters. These resources each have one or more representations that are publically accessible. For example, Craigslist might have two representations of its ads: the HTML version, which is meant to be read by a web browser, and an XML version for local papers' servers, desktop clients, etc..

How do we know which representation to serve up? We rely on the client to tell us which it prefers. When a client sends an HTTP request, it should include an Accept header, specifying what the format(s) it understands. A web browser, for example, should ask for HTML in its Accept header.

In our Rails apps, we use respond_to blocks to return the appropriate response based on the Accept header. Here are our show and update method, rewritten with respond_to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class EmployeesController < ActionController::Base
        # ...
        def show
                @employee = Employee.find(params[:id])
                respond_to do |format|
                        # Because no block is supplied for format.html,
                        # we will just render the show action like normal
                        format.html
                        format.xml do
                                render(
                                        :xml => @employee.to_xml,
                                        :status => :ok
                                )
                        end
                end
        end
        
        def update
                @employee = Employee.find(params[:id])
                respond_to do |format|
                        if @employee.update_attributes(params[:employee])
                                # Saved successfully
                                format.html do
                                        redirect_to employee_url(@employee)
                                end
                                # The :location parameter tells the client where
                                # the newly-created resource can be found
                                format.xml { render :xml => @employee.to_xml, :status => :ok, :location => url_for(@employee) }
                        else
                                # Validation errors - didn't save
                                format.html { render :action => 'edit' }
                                format.xml do
                                        render(
                                                :xml => @employee.errors.to_xml,
                                                :status => :unproccessable_entity
                                        )
                                end
                        end
                end
        end
        # ...
end

Besides respond_to, the above example illustrates another important piece of REST on Rails: HTTP status codes. Look at this snippet from the code above:

1
2
3
4
render(
        :xml => @employee.errors.to_xml,
        :status => :unproccessable_entity
)

The :status parameter tells Rails to send back a certain HTTP status code in the response header. Instead of forcing you to remember the numerical codes, Rails lets you use more memorable symbols like :unproccessable_entity. A complete code-to-symbol mapping is available. Here are the ones I use the most:

  • 200 :ok—successful update, destroy, show, or index
  • 201 :created—successful create
  • 422 :unprocessable_entity—invalid submission to create or update
  • 406 :not_acceptable—the Accept headers don't match any format in the respond_to block

When the client requests HTML, Rails typically generates the body of the response from one of your templates. But in the example above, notice how we explicitly call render when XML is requested. This is just a convenient way to generate an XML document; we could have also written an XML template ourselves.

What if you don't want to reveal the entire record in your XML response? As it stands, our code will give the client access to every attribute of the Employee model. We may want to keep certain attributes private, like a password or a Social Security number. To do that, we pass parameters to the to_xml method:

1
2
3
4
5
render(
        # Leave out the Social Security number
        :xml => @employee.to_xml(:except => :ssn),
        :status => :ok
)

RESTful forms

Rails expects the client to use the correct HTTP methods (GET, POST, PUT, and DELETE). Unfortunately, most web browsers don't support PUT and DELETE. There's a somewhat hackish workaround for that: including _method in the request parameters. That means putting hidden fields in our forms:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- This is a template for the create action.
Don't do this. I will show you a better way shortly. -->
<% form_for :employee, employee_url(@employee) do |f| %>
        <!-- include _method in the params -->
        <% hidden_field_tag '_method', 'put' %>
        
        <p>
                <%= f.label :name %>
                <%= f.text_field :name %>
        </p>
        
        <!-- and so on -->
<% end %>

You only need to include the _method field in edit forms. For deleting records, you should use button_to with :method => :delete.

Remember, the request must include _method in the parameters anytime you invoke an update or destroy action. Otherwise, the routing won't work.

Fortunately, though, if you're using form_for, you needn't trouble yourself with inserting the _method input. There's a way to make Rails do that for you. Here it is:

1
2
3
4
5
6
7
8
9
10
<% form_for @employee do |f| %>
        <!-- the hidden tag will be automatically generated if necessary -->
        
        <p>
                <%= f.label :name %>
                <%= f.text_field :name %>
        </p>
        
        <!-- and so on -->
<% end %>

Based on whether or not @employee is a new_record?, Rails will determine the appropriate URL and method. This saves you a lot of keystrokes and yields cleaner code, especially if you want to use the same templates for new and edit, which is often the case.

ActiveResource

The final piece of the Rails REST puzzle is ActiveResource. So far, we've only discussed coding on the server side of RESTful communications. ActiveResource is the other half of the equation: it's a client library that makes it easy to consume RESTful resources.

One of the great things about ActiveResource is that it's modelled after ActiveRecord. The interface is very similar, albeit somewhat simpler.

Suppose you want to create, read, update, and delete ads on Craigslist. First, you'd create a new file in your models directory:

1
2
3
4
5
6
7
8
9
# Very similar to an ActiveRecord model
class Ad < ActiveResource::Base
        # ActiveResource needs to know the location
        # of the resource we're connecting to,
        # plus the login, if required
        self.site = 'http://craigslist.com/ads'
        self.user = 'some_username' # If needed
        self.password = 'some_password' # If needed
end

And there you have it. As with ActiveRecord, you don't have to write much code to get the basic CRUD functionality. Now you can do things like:

1
2
3
4
Ad.create(
        :title => 'Alaskan State Jet For Sale',
        :body => '50% off with campaign contribution'
)

ActiveResource will handle sending the HTTP request for you.

Just remember: ActiveResource expects the remote service to conform to Rails-style RESTful URLs. If it doesn't, you can still use custom methods (as explained in the documentation), but it's a bit of a pain.

Unforunately, as of this writing, the error messages in ActiveResource are rather unhelpful. If the server diverges ever so slightly from the conventions ActiveResource expects, you might get a very weird error. A future articles will provide more detail on writing controllers for ActiveResource compatibility.

Putting it all together

On the server side, you need to use four things:

  • Routing
  • RESTful controller design
  • respond_to blocks and HTTP responses
  • RESTful forms

On the client side, you'll use ActiveResource.

Remember, there are some cases where a machine is at times the client and at other times the server. In those situations, you'd use both ActiveResource and the server-side technologies in the same Rails app.

Rails version problems

Some of the functionality discussed in this article is missing or broken in one or more stable releases of Rails. For example, in an InstantRails package I downloaded, ActiveRecord::Base.user and ActiveRecord::Base.password were absent. That was Rails 2.0.x. Upgrading to 2.1.1 fixed it for me.

Similarly, the default configs on your webhost may use an older version of Rails. Check that your host provides a more recent version, and tell your app to use it in environment.rb.

Comment on this article

All fields are optional (other than the comment itself, of course).