Chapter 7. A Service Implementation

It’s been a while since I presented any code. Indeed, coming up with the code is currently a general problem for REST advocates. Despite the simplicity of REST, right now there are few well-known services that show off its principles. The average web service has an architecture that combines elements of REST with the RPC style. This is changing, of course, and this book is part of the wave of change. Another problem is that many services seem trivial when exposed through resources, even though they’d look very impressive as SOAP/WSDL services. See Appendix A for a partial list of real RESTful services with enough moving parts to learn from.

Until recently, web frameworks made few concessions to the lucrative REST market. They focus on applications for web browsers, using only the GET and POST methods of HTTP. You can implement RESTful services with just GET and POST, but the limitation seems to encourage the RPC style instead. New frameworks for RESTful services are showing up, though, and existing frameworks are changing to accommodate REST fans. Django (Python), Restlet (Java), and Ruby on Rails all make it easy to expose resources that respond to HTTP’s uniform interface. I cover these frameworks in Chapter 12. In this chapter I use Ruby on Rails as a medium for demonstrating how to implement a real-world web service.

A Social Bookmarking Web Service

Back in Chapter 2 I introduced del.icio.us, a web site that lets you publicly post bookmarks, tag them with short metadata strings, and see which URIs other people have posted. There’s also a del.icio.us web service, which I used as the target of the web service clients in Chapter 2.

I mentioned that the del.icio.us web service has a couple shortcomings. First, it’s a REST-RPC hybrid, not a fully RESTful service. It only exposes resources by accident and it doesn’t respect HTTP’s uniform interface. Second, the web service only gives you access to your own bookmarks and tags. When you use the service, it looks like you’re the only person on del.icio.us. In this chapter I use Ruby on Rails to develop a RESTful web service has much of the functionality of the del.icio.us web service and the del.icio.us web site.

I’ve got three goals for this chapter. Previous chapters showed you service design from first principles. Here, I want to show you how to make a RESTful, resource-oriented service out of an existing RPC-style service. Second, I want to show you the sort of tradeoffs you might need to make to get a design that works within your chosen framework. Finally, I want to show you the complete code to a nontrivial web service, without boring you with page after page of implementation details. I chose Ruby on Rails as my framework because Ruby is a dynamic language, and Rails comes with a lot of helper classes. This makes it easy to illustrate the underlying concepts in just a few lines of code. What’s more, the most recent version of Rails is explicitly designed around the principles of REST and resource-oriented design.

My challenge is to reconcile the constraints imposed by the Resource-Oriented Architecture, and my own design sensibilities, with the simplifying assumptions of the Rails framework. My resource design is heavily informed by what Rails itself considers good design, but at points I’ve had to hack Rails to get the behavior I want, instead of the behavior Rails creator David Heinemeier Hansson wants. Rails imposes more constraints than most frameworks (this is a big reason for its success, actually), but your choice of framework will always have some effect on your design.

I’m going to start with an empty Rails 1.2 application, and fill in the details as the design takes shape. I created a Rails application with the following command:

$ rails bookmarks

I installed two Rails plugins I know I’ll need: acts_as_taggable, to implement tags on bookmarks, and http_authentication, to tie HTTP Basic authentication into my user model. I’ve also installed the atom-tools Ruby gem, so I can generate Atom feeds for representations.

$ cd bookmarks 
$ script/plugin install acts_as_taggable 
$ script/plugin install http_authentication
$ gem install atom-tools

I also created a SQL database called bookmarks_development, and configured config/database.yaml so that Rails can connect to the database.

Figuring Out the Data Set

Because I’m basing my service on an existing one, it’s fairly easy to figure out the parameters of the data set. If what follows is confusing, feel free to flip back to del.icio.us: The Sample Application” in Chapter 2 for an overview of del.icio.us.

The del.icio.us site has four main kinds of data: user accounts, bookmarks (del.icio.us calls them “posts”), tags (short strings that act as metadata for bookmarks), and bundles (collections of tags for a user). The web site and the web service track the same data set.

Unlike an S3 bucket, or a user account on my map service, a del.icio.us user account is not just a named list of subordinate resources. It’s got state of its own. A del.icio.us account has a username and password, but it’s supposed to correspond to a particular person, and it also tracks that person’s full name and email address. A user account also has a list of subordinate resources: the user’s bookmarks. All this state can be fetched and manipulated through HTTP.

A bookmark belongs to a user and has six pieces of state: a URI, a short and a long description, a timestamp, a collection of tags, and a flag that says whether or not it’s public (the previous chapter’s “custom place” resource has a similar flag). The client is in charge of specifying all of this information for each bookmark, though the URI and the short description are the only required pieces of state.

The URIs in users’ bookmarks are the most interesting part of the data set. When you put a bunch of peoples’ bookmarks together, you find that the URIs have emergent properties. On del.icio.us these properties include newness, a measure of how recently someone bookmarked a particular URI; “popularity,” a measure of how many people have bookmarked that URI; and the “tag cloud,” a generated vocabulary for the URI, based on which tags people tend to use to describe the URI. The del.icio.us web site also exposes a recommendation engine that relates URIs to each other, using a secret algorithm.

I’m not going to do much with the emergent properties of URIs, properties that account for much of del.icio.us’s behind-the-scenes code. My implemented service will have a notion of newness but it won’t have popularity, tag clouds, or recommendation algorithms. This is just so I can keep this book down to a manageable size instead of turning it into a book about recommendation algorithms.

Tags have only one piece of state: their name. They only exist in relation to bookmarks—and bundles, which I haven’t described yet. A bundle is a user’s decision to group particular tags together. A user with tags “recipes,” “restaurants,” and “food,” might group those tags into a bundle called “gustation.” I’ll show the RESTful design of bundles, just for completeness, but I won’t be implementing them when it comes time to write code.

At this point I know enough about the data set to create the database schema. I create an empty database called bookmarks_development in my MySQL installation, and put this data in the file db/migrate/001_initial_schema.rb, shown in Example 7-1.

Example 7-1. The bookmark database schema as a Rails migration
class InitialSchema < ActiveRecord::Migration

  # Create the database tables on a Rails migration.
  def self.up
    # The 'users' table, tracking four items of state 
    # plus a unique ID.
    create_table :users, :force => true do |t|
      t.column :user_id, :string
      t.column :name, :string
      t.column :full_name, :string
      t.column :email, :string
      t.column :password, :string
    end

    # The 'bookmarks' table, tracking six items of state,
    # plus a derivative field and a unique ID.
    create_table :bookmarks, :force => true do |t|
      t.column :bookmark_id, :string
      t.column :uri, :string
      t.column :uri_hash, :string   # A hash of the URI.
                                    # See book text below.
      t.column :short_description, :string
      t.column :long_description, :text
      t.column :timestamp, :datetime
      t.column :public, :boolean
    end

    # This join table reflects the fact that bookmarks are subordinate
    # resources to users.
    create_table :user_bookmarks, :force => true do |t|
      t.column :user_id, :integer
      t.column :bookmark_id, :integer
    end

    # These two are standard tables defined by the acts_as_taggable
    # plugin, of which more later. This one defines tags.
    create_table :tags do |t|
      t.column :name, :string
    end

    # This one defines the relationship between tags and the things
    # tagged--in this case, bookmarks.
    create_table :taggings do |t|
      t.column :tag_id, :integer
      t.column :taggable_id, :integer
      t.column :taggable_type, :string
    end

    # Four indexes that capture the ways I plan to search the
    # database.
    add_index :users, :name
    add_index :bookmarks, :uri_hash
    add_index :tags, :name
    add_index :taggings, [:tag_id, :taggable_id, :taggable_type]
  end
  # Drop the database tables on a Rails reverse migration.
  def self.down
    [:users, :bookmarks, :tags, :user_bookmarks, :taggings].each do |t|
      drop_table t
    end
  end
end

I’ve used Ruby code to describe five database tables and four indexes. I create the corresponding database schema by running this command:

$ rake db:migrate

Resource Design

In Chapters 5 and 6 I had a lot of leeway in turning my imaginary data set into resources. The idea for my map service came from the Google Maps application with its image tiles, but I took it off in another direction. I added user accounts, custom places, and other features not found in any existing map service.

This chapter works differently. I’m focusing on translating the ideas of del.icio.us into the Resource-Oriented Architecture. There are lots of ways of exposing a data set of tagged bookmarks, but I’m focusing on the ones del.icio.us actually uses. Let’s start by taking a look at what the del.icio.us web service has to offer.

The del.icio.us web service is a REST-RPC hybrid service, described in English prose at http://del.icio.us/help/api/. The web service itself is rooted at https://api.del.icio.us/v1/. The service exposes three RPC-style APIs, rooted at the relative URIs posts/, tags/, and bundles/. Beneath these URIs the web service exposes a total of twelve RPC functions that can be invoked through HTTP GET. I need to define RESTful resources that can expose at least the functionality of these three APIs:

First, the posts/ API, which lets the user fetch and manage her bookmark posts to del.icio.us:

  • posts/get: Search your posts by tag or date, or search for a specific bookmarked URI.

  • posts/recent: Fetch the n most recent posts by the authenticated user. The client may apply a tag filter: “fetch the n most recent posts that the authenticated user tagged with tag t”.

  • posts/dates: Fetch the number of posts by the authenticated user for each day: perhaps five posts on the 12th, two on the 15th, and so on. The client may apply a tag filter here, too.

  • posts/all: Fetch all posts for the authenticated user, ever. The client may apply a tag filter.

  • posts/update: Check when the authenticated user last posted a bookmark. Clients are supposed to check this before deciding to call the expensive posts/all.

  • posts/add: Create a bookmark for a URI. The client must specify a short description. It may choose to specify a long description, a set of tags, and a timestamp. A bookmark may be public or private (the default is public). A client may not bookmark the same URI more than once: calling posts/add again overwrites the old post with new information.

  • posts/delete: Deletes a user’s post for a particular URI.

Second, the tags/ API, which lets the authenticated user manage her tags separately from the bookmarks that use the tags:

  • tags/get: Fetch a list of tags used by the authenticated user.

  • tags/rename: Rename one of the authenticated user’s tags. All posts tagged with the old name will now be tagged with the new name instead.

Finally, the bundles API, which lets the authenticated user group similar tags together.

  • tags/bundles/all: Fetch the user’s bundles. The resulting document lists the bundles, and each bundle lists the tags it contains.

  • tags/bundles/set: Group several tags together into a (possibly new) bundle.

  • tags/bundles/delete: Delete a bundle.

That’s the web service. As I mentioned in Chapter 2, the service only gives you access to your own bookmarks and tags. The del.icio.us web site has social features as well, and I’m going to steal some of those features for my design.

Here are some interesting “functions” exposed by the del.icio.us web site but not the web service:

  • /{username}: Fetch any user’s bookmarks.

  • /{username}/{tag}: Fetch any user’s bookmarks, applying a tag filter.

  • /tag/{tag-name}: Fetch bookmarks tagged with a particular tag, from all users.

  • /url/{URI-MD5}: Fetch the list of users who have bookmarked a particular URI. The {URI-MD5} happens to be the MD5 hash of the URI, but from the average client’s point of view that’s not important: it’s an opaque string of bytes that somehow identifies a URI within the del.icio.us system.

  • /recent: Fetch the most recently posted bookmarks, from all users. The del.icio.us home page also shows this information.

Now that I know what the service has to do, arranging the features into resources is like working a logic puzzle. I want to expose as few kinds of resources as possible. But one kind of resource can only convey one concept, so sometimes I need to split a single feature across two kinds of resource. On the other hand, sometimes I can combine multiple RPC functions into one kind of resource, a resource that responds to several methods of HTTP’s uniform interface.

REST in Rails

I’m not designing these resources in a vacuum: I’m going to implement them in a Rails application. It’s worth taking a brief look at how RESTful applications work in Rails. Unlike some other frameworks, Rails doesn’t let you define your resources directly. Instead, it divides up an application’s functionality into controllers: it’s the controllers that expose the resources. The first path variable in a request URI is used to route Rails to the appropriate controller class. For instance, in the URI /weblogs/4 the “weblogs” designates the controller: probably a class called WeblogController. The “4” designates the database ID of a particular weblog.

In previous versions of Rails, programmers defined RPC-style methods on controllers: methods like rename and delete. To rename a weblog you’d send a GET or an overloaded POST request to /weblogs/4/rename. Rails applications, like most web applications, were REST-RPC hybrids.

In Rails 1.2, programmers define special controller methods that correspond to the methods of HTTP’s uniform interface. For instance, sending a GET to /weblogs triggers the WeblogController’s index method, which is supposed to retrieve a list of the weblogs. Sending a POST to the same URI triggers the WeblogController#create method, which creates a subordinate resource beneath /weblogs: say, a weblog with a URI of /weblogs/4. The Rails controller exposes a resource—“the list of weblogs”—that responds to GET and POST. As you’d expect, when you POST to the “list” resource you get a subordinate resource: a new weblog.

The subordinate resource also supports the uniform interface. If you wanted to rename a weblog in an RPC-style service, you might POST a new name to /weblogs/4/rename. Under a RESTful regime, you PUT a new name to /weblogs/4, triggering the WeblogController#update method. To delete a weblog, you send a DELETE request to its URI, triggering the controller’s WeblogController#destroy method. There’s no need to expose an RPC-style URI /weblogs/4/delete, because HTTP’s uniform interface already knows about deleting.

These two resources, a list and an item in the list, show up all the time. Every database table is a list that contains items. Anything that can be represented as an RSS or Atom feed is a list that contains items. Rails defines a RESTful architecture that makes a simplifying assumption: every resource you expose can be made to fit one of these two patterns. This makes things easy most of the time, but the cost is aggravation when you try to use Rails controllers to expose resources that don’t fit this simple model.

I’m going to define my resources in terms of Rails controllers. These controllers impose constraints on my URI structure and my use of the uniform interface, and I need to design with those constraints in mind. By the time I’m done designing the controllers, I’ll know which resources the controllers expose, which URIs they answer to, and which methods of the uniform interface correspond to which RPC functions from the del.icio.us service. Basically, I’ll have completed steps 2 through 4 of the 9-step procedure from the Turning Requirements into Read/Write Resources” section in Chapter 6: “Split the data set into resources,” “Name the resources with URIs,” and “Expose a subset of the uniform interface.” In Chapter 12 I give a variant of the service design procedure specifically for Rails services.

I’ll only be accessing my Rails application from my local machine. The root URI will be http://localhost:3000/v1. When I give a relative URI below, like /users, understand that I’m talking about http://localhost:3000/v1/users. I only ever plan to write one version of this service, but I’m versioning the URIs, just in case. (When and how to version is discussed in Chapter 8).

The User Controller

Now I’m going to go back to that big list of RPC functions I found in the del.icio.us API and web site, and try to tease some Rails controllers out of it. One obvious controller is one that exposes information about user accounts. In Rails, this would be a class called UsersController. As soon as I say that, a lot of decisions are made for me. Rails sets up a path of least resistance that looks like this:

The user controller exposes a one-off “user list” resource, at the URI /users. It also exposes a resource for every user on the system, at a URI that incorporates the user’s database ID: /users/52 and the like. These resources expose some subset of HTTP’s uniform interface. Which subset? Rails defines this with a programming-language interface in the superclass of all controller classes: ActionController::Base. Table 7-1 shows how the two interfaces line up.

Table 7-1. How Rails wants my UsersController to look
OperationHTTP actionRails method
List the usersGET /usersUsersController#index
Create a userPOST /usersUsersController#create
View a userGET /users/52UsersController#show
Modify a userPUT /users/52UsersController#update
Delete a userDELETE /users/52UsersController#destroy

So if I want to let clients create new user accounts, I implement UsersController#create, and my “user list” resource starts calling that method in response to POST requests.

The path of least resistance is pretty good but I have a couple problems with it. First, I don’t want to let clients fetch the list of users, because del.icio.us doesn’t have that feature. (Presumably the del.icio.us administrative interface does have a feature like this.) That’s fine: I don’t have to expose GET on every resource, and I don’t have to define index in every controller. My user list resource, at the URI /users, will only expose the POST method, for creating new users. My user list is a featureless container for user account resources, and the only thing a client can do with it is create a new account. This incorporates functionality like that at https://secure.del.icio.us/register, where you can use your web browser to sign up for a del.icio.us account.

Another problem is that URIs like /users/52 look ugly. They certainly don’t look like http://del.icio.us/leonardr, the URI to my corresponding page on del.icio.us. This URI format is the Rails default because every object in a Rails application’s database can be uniquely identified by its table (“users”) and its ID (“52”). This URI might go away (if user 52 DELETEs her account), but it will never change, because database unique IDs don’t change.

I’d rather expose readable URIs that might change occasionally than permanent URIs that don’t say anything, so I’m going to identify a user using elements of its resource state. I happen to know that users have unique names, so I’m going to expose my “user” resources at URIs like /users/leonardr. Each resource of this type will expose the methods GET, PUT, and DELETE. This incorporates the functionality of the del.icio.us web site’s /{username} “function.” It also incorporates the pages on the web site (I didn’t mention these earlier) that let you edit and delete your own del.icio.us account.

To expose this RESTful interface, I just need to implement four special methods on UsersController. The create method implements POST on the “user list” resource at /users. The other three methods implement HTTP methods on the “user” resources at /users/{username}: show implements GET, update implements PUT, and destroy implements DELETE.

The Bookmarks Controller

Each user account has a number of subordinate resources associated with it: the user’s bookmarks. I’m going to expose these resources through a second controller class, rooted beneath the “user account” resource.

The base URI of this controller will be /users/{username}/bookmarks. Like the users controller, the bookmarks controller exposes two types of resource: a one-off resource for the list of a user’s bookmarks, and one resource for each individual bookmark.

Rails wants to expose an individual bookmark under the URI /users/{username}/bookmarks/{database-id}. I don’t like this any more than I like /users/{database-id}. I’d like the URI to a bookmark to have some visible relationship to the URI that got bookmarked.

My original plan was to incorporate the target URI in the URI to the bookmark. That way if I bookmarked http://www.oreilly.com/, the bookmark resource would be available at /v1/users/leonardr/bookmarks/http://www.oreilly.com/. Lots of services work this way, including the W3C’s HTML validator. Looking at one of these URIs you can easily tell who bookmarked what. Rails didn’t like this URI format, though, and after trying some hacks I decided to get back on Rails’s path of least resistance. Instead of embedding external URIs in my resource URIs, I’m going to put the URI through a one-way hash function and embed the hashed string instead.

If you go to http://del.icio.us/url/55020a5384313579a5f11e75c1818b89 in your web browser, you’ll see the list of people who’ve bookmarked http://www.oreilly.com/. There’s no obvious connection between the URI and its MD5 hash, but if you know the former you can calculate the latter. It’s certainly better than a totally opaque database ID. And since it’s a single alphanumeric string, Rails handles it with ease. My bookmark resources will have URIs like /v1/users/leonardr/bookmarks/55020a5384313579a5f11e75c1818b89. That URI identifies the time I bookmarked http://www.oreilly.com/ (see Example 7-2).

Example 7-2. Calculating an MD5 hash in Ruby
require 'digest/md5'
Digest::MD5.new("http://www.oreilly.com/").to_s
# => "55020a5384313579a5f11e75c1818b89"

When a user is first created it has no bookmarks. A client creates bookmarks by sending a POST request to its own “bookmark list” resource, just as it might create a user account by sending a POST to the “user list” resource. This takes care of the posts/add and posts/delete functions from the del.icio.us API.

Unlike with the list of users, I do want to let clients fetch the list of a user’s bookmarks. This means /users/{username}/bookmarks will respond to GET. The individual bookmarks will respond to GET, PUT, and DELETE. This means the BookmarksController: index, create, show, update, and delete.

The “bookmark list” resource incorporates some of the functionality from the del.icio.us API functions posts/get, posts/recent, and posts/all.

The User Tags Controller

Bookmarks aren’t the only type of resource that conceptually fits “beneath” a user account. There’s also the user’s tag vocabulary. I’m not talking about tags in general here: I’m asking questions about which tags a particular user likes to use. These questions are handled by the user tags controller.

This controller is rooted at /users/{username}/tags. That’s the “user tag list” resource. It’s an algorithmic resource, generated from the tags a user uses to talk about her bookmarks. This resource corresponds roughly to the del.icio.us tags/get function. It’s a read-only resource: a user can’t modify her vocabulary directly, only by changing the way she uses tags in bookmarks.

The resources at /users/{username}/tags/{tag} talk about the user’s use of a specific tag. My representation will show which bookmarks a user has filed under a particular tag. This class of resource corresponds to the /{username}/{tag} “function” from the web site. It also incorporates some stuff of the del.icio.us API functions posts/get, posts/recent, and posts/all.

The “tag” resources are also algorithmic, but they’re not strictly read-only. A user can’t delete a tag except by removing it from all of her bookmarks, but I do want to let users rename tags. (Tag deletion is a plausible feature, but I’m not implementing it because, again, del.icio.us doesn’t have it.) So each user-tag resource will expose PUT for clients who want to rename that tag.

Instead of PUT, I could have used overloaded POST to define a one-off “rename” method like the del.icio.us API’s tag/rename. I didn’t, because that’s RPC-style thinking. The PUT method suffices to convey any state change, whether it’s a rename or something else. There’s a subtle difference between renaming the tag and changing its state so the name is different, but it’s the difference between an RPC-style interface and a uniform, RESTful one. It’s less work to program a computer to understand a generic “change the state” than to program it to understand “rename a tag.”

The Calendar Controller

A user’s posting history—her calendar— is handled by one more controller that lives “underneath” a user account resource. The posting history is another algorithmically generated, read-only resource: you can’t change your posting history except by posting. The controller’s root URI is /users/{username}/calendar, and it corresponds to the del.icio.us API’s posts/dates function.

I’ll also expose a variety of subresources, one for each tag in a user’s vocabulary. These resources will give a user’s posting history when only one tag is considered. These resources correspond to the del.icio.us API’s posts/dates function with a tag filter applied. Both kinds of resource, posting history and filtered posting history, will expose only GET.

The URI Controller

I mentioned earlier that URIs in a social bookmarking system have emergent properties. The URI controller gives access to some of those properties. It’s rooted at /uris/, and it exposes URIs as resources independent from the users who bookmark them.

I’m not exposing this controller’s root URI as a resource, though I could. The logical thing to put there would be a huge list of all URIs known to the application. But again, the site I’m taking for my model doesn’t have any feature like that. Instead, I’m exposing a series of resources at /uris/{URI-MD5}: one resource for each URI known to the application. The URI format is the same as /users/{username}/bookmarks/{URI-MD5} in the user bookmark controller: calculate the MD5 hash of the target URI and stick it onto the end of the controller’s base URI.

These resources expose the application’s knowledge about a specific URI, such as which users have bookmarked it. This corresponds to the /url/{URI-MD5} “function” on the del.icio.us web site.

The Recent Bookmarks Controller

My last implemented controller reveals another emergent property of the URIs. In this case the property is newness: which URIs were most recently posted.

This controller is rooted at /recent. The top-level “list” resource lists all the recently posted bookmarks. This corresponds to the /recent “function” on the del.icio.us web site.

The sub-resources at /recent/{tag} expose the list of recently posted bookmarks that were tagged with a particular tag. For instance, a client can GET /recent/recipes to find recently posted URIs that were tagged with “recipes”. This corresponds to the /tag/{tag-name} function on the del.icio.us web site.

The Bundles Controller

Again, I’m not going to implement this controller, but I want to design it so you can see I’m not cheating. This controller is rooted at /user/{username}/bundles/. An alternative is /user/{username}/tags/bundles/, but that would prevent any user from having a tag named “bundles”. A client can send a GET request to the appropriate URI to get any user’s “bundle list”. A client can POST to its own bundle list to create a new bundle. This takes care of tags/bundles/all and part of tags/bundles/set.

The sub-resources at /user/{username}/bundles/{bundle} expose the individual bundles by name. These respond to GET (to see which tags are in a particular bundle), PUT (to modify the tags associated with a bundle), and DELETE (to delete a bundle). This takes care of tags/bundles/delete and the rest of tags/bundles/set.

The Leftovers

What’s left? I’ve covered almost all the functionality of the original del.icio.us API, but I haven’t placed the posts/update function. This function is designed to let a client avoid calling posts/all when there’s no new data there. Why bother? Because the posts/all function is extremely expensive on the server side. A del.icio.us client is supposed to keep track of the last time it called posts/all, and check that time against the “return value” of posts/update before calling the expensive function again.

There’s already a solution for this built into HTTP: conditional GET. I cover it briefly in Conditional GET later in this chapter and I’ll cover it in more detail in Chapter 8, but in this chapter you’ll see it implemented. By implementing conditional GET, I can give the time- and bandwidth-saving benefits of posts/update to most of the resources I’m exposing, not just the single most expensive one.

Remodeling the REST Way

I’ve taken an RPC-style web service that was only RESTful in certain places and by accident, and turned it into a set of fully RESTful resources. I’d like to take a break now and illustrate how the two services line up with each other. Tables 7-2 through 7-6 show every social bookmarking operation I implemented, the HTTP request you’d send to invoke that operation on my RESTful web service, and how you’d invoke the corresponding operation on del.icio.us itself.

Table 7-2. Service comparison: user accounts
OperationOn my serviceOn del.icio.us
Create a user accountPOST /usersPOST /register (via web site)
View a user accountGET /users/{username}GET /users/{username} (via web site)
Modify a user accountPUT /users/{username}Various, via web site
Delete a user accountDELETE /users/{username}POST /settings/{username}/profile/delete (via web site)
Table 7-3. Service comparison: bookmark management
OperationOn my serviceOn del.icio.us
Post a bookmarkPOST /users/{username}/bookmarksGET /posts/add
Fetch a bookmarkGET /users/{username}/bookmarks/{URI-MD5}GET /posts/get
Modify a bookmarkPUT /users/{username}/bookmarks/{URI-MD5}GET /posts/add
Delete a bookmarkDELETE /users/{username}/bookmarks/{URI-MD5}GET /posts/delete
See when the user last posted a bookmarkUse conditional HTTP GETGET /posts/update
Fetch a user’s posting historyGET /users/{username}/calendarGET /posts/dates (your history only)
Fetch a user’s posting history, filtered by tagGET /users/{username}/calendar/{tag}GET /posts/dates with query string (your history only)
Table 7-4. Service comparison: finding bookmarks
OperationOn my serviceOn del.icio.us
Fetch a user’s recent bookmarksGET /users/{username}/bookmarks with query stringGET /posts/recent (your bookmarks only)
Fetch all of a user’s bookmarksGET /users/{username}/bookmarksGET /posts/all (your bookmarks only)
Search a user’s bookmarks by dateGET /users/{username}/bookmarks with query stringGET /posts/get with query string (your bookmarks only)
Fetch a user’s bookmarks tagged with a certain tagGET /users/{username}/bookmarks/{tag}GET /posts/get with query string (your bookmarks only)
Table 7-5. Service comparison: social features
OperationOn my serviceOn del.icio.us
See recently posted bookmarksGET /recentGET /recent (via web site)
See recently posted bookmarks for a certain tagGET /recent/{tag}GET /tag/{tag} (via web site)
See which users have bookmarked a certain URIGET /uris/{URI-MD5}GET /url/{URI-MD5} (via web site)
Table 7-6. Service comparison: tags and tag bundles
OperationOn my serviceOn del.icio.us
Fetch a user’s tag vocabularyGET /users/{username}/tagsGET /tags/get (your tags only)
Rename a tagPUT /users/{username}/tags/{tag}GET /tags/rename
Fetch the list of a user’s tag bundlesGET /users/{username}/bundlesGET /tags/bundles/all (your bundles only)
Group tags into a bundlePOST /users/{username}/bundlesGET /tags/bundles/set
Fetch a bundleGET /users/{username}/bundles/{bundle}N/A
Modify a bundlePUT /users/{username}/bundles/{bundle}GET /tags/bundles/set
Delete a bundleDELETE /users/{username}/bundles/{bundle}GET /tags/bundles/delete

I think you’ll agree that the RESTful service is more self-consistent, even accounting for the fact that some of the del.icio.us features come from the web service and some from the web site. Table 7-6 is probably the best for a straight-up comparison. There you can distinctly see the main advantage of my RESTful service: its use of the HTTP method to remove the operation name from the URI. This lets the URI identify an object in the object-oriented sense. By varying the HTTP method you can perform different operations on the object. Instead of having to understand some number of arbitrarily-named functions, you can understand a single class (in the object-oriented sense) whose instances expose a standardized interface.

My service also lifts various restrictions found in the del.icio.us web service. Most notably, you can see other peoples’ public bookmarks. Now, sometimes restrictions are the accidental consequences of bad design, but sometimes they exist for a reason. If I were deploying this service commercially it might turn out that I want to add those limits back in. I might not want user A to have unlimited access to user B’s bookmark list. I don’t have to change my design to add these limits. I just have to change the authorization component of my service. I make it so that authenticating as userA doesn’t authorize you to fetch userB’s public bookmarks, any more than it authorizes you to delete userB’s account. Or if bandwidth is the problem, I might limit how often any user can perform certain operations. I haven’t changed my resources at all: I’ve just added additional rules about when operations on those resources will succeed.

Implementation: The routes.rb File

Ready for some more code? I’ve split my data set into Rails controllers, and each Rails controller has divided its data set further into one or two kinds of resources. Rails has also made decisions about what my URIs will look like. I vetoed some of these decisions (like /users/52, which I changed to /users/leonardr), but most of them I’m going to let stand.

I’ll implement the controllers as Ruby classes, but what about the URIs? I need some way of mapping path fragments like bookmarks/ to controller classes like BookmarksController. In a Rails application, this is the job of the routes.rb file. Example 7-3 is a routes.rb that sets up URIs for the six controllers I’ll implement later in the chapter.

Example 7-3. The routes.rb file
# service/config/routes.rb
ActionController::Routing::Routes.draw do |map|
  base = '/v1'

  ## The first controller I define is the UsersController. The call to
  ## map.resources sets it up so that all HTTP requests to /v1/users
  ## or /v1/users/{username} are routed to the UsersController class.

  # /v1/users => UsersController
  map.resources :users, :path_prefix => base

  ## Now I'm going to define a number of controllers beneath the
  ## UsersController. They will respond to requests for URIs that start out
  ## with /v1/users/{username}, and then have some extra stuff.
  user_base = base + '/users/:username'

  # /v1/users/{username}/bookmarks => BookmarksController
  map.resources :bookmarks, :path_prefix => user_base

  # /v1/users/{username}/tags => TagsController
  map.resources :tags, :path_prefix => user_base

  # /v1/users/{username}/calendar => CalendarController
  map.resources :calendar, :path_prefix => user_base

  ## Finally, two more controllers that are rooted beneath /v1.

  # /v1/recent => RecentController
  map.resources :recent, :path_prefix => base

  # /v1/uris => UrisController
  map.resources :uris, :path_prefix => base
end

Now I’m committed to defining six controller classes. The code in Example 7-3 determines the class names by tying into Rails’ naming conventions. My six classes are called UsersController, BookmarksController, TagsController, CalendarController, RecentController, and UrisController. Each class controls one or two kinds of resources. Each controller implements a specially-named Ruby method for each HTTP method the resources expose.

Design the Representation(s) Accepted from the Client

When a client wants to modify a user account or post a bookmark, how should it convey the resource state to the server? Rails transparently supports two incoming representation formats: form-encoded key-value pairs and the ActiveRecord XML serialization format.

Form-encoding should be familiar to you. I mentioned it back in Chapter 6, and it’s everywhere in web applications. It’s the q=jellyfish and color1=blue&color2=green you see in query strings on the human web. When a client makes a request that includes the query string color1=blue&color2=green, Rails gives the controller a hash that looks like this:

{"color1" => "blue", "color2" => "green"}

The service author doesn’t have to parse the representation: they can work directly with the key-value pairs.

ActiveRecord is Rails’s object-relational library. It gives a native Ruby interface to the tables and rows in a relational database. In a Rails application, most exposed resources correspond to these ActiveRecord tables and rows. That’s the case for my service: all my users and bookmarks are database rows managed through ActiveRecord.

Any ActiveRecord object, and the database row that underlies it, can be represented as a set of key-value pairs. These key-value pairs can be form-encoded, but ActiveRecord also knows how to encode them into XML documents. Example 7-4 gives an XML depiction of an ActiveRecord object from this chapter: a user account. This is the string you’d get by calling to_xml on a (yet-to-be-defined) User object. Example 7-5 gives an equivalent form-encoded representation. Example 7-6 gives the hash that’s left when Rails parses the XML document or the form-encoded string as an incoming representation.

Example 7-4. An XML representation of a user account
<user>
 <name>leonardr</name>
 <full-name>Leonard Richardson</full-name>
 <email>leonardr@example.com</email>
 <password>mypassword</password> 
</user>
Example 7-5. A form-encoded representation of a user account
user[name]=leonardr&user[full-name]=Leonard%20Richardson
&user[email]=leonardr%40example.com&user[password]=mypassword
Example 7-6. A set of key-value pairs derived from XML or the form-encoded representation
{ "user[name]" => "leonardr",
  "user[full_name]" => "Leonard Richardson",
  "user[email]" => "leonardr@example.com",
  "user[password]" => "mypassword" }

I’m going to support both representation formats. I can do this by defining my keys for the form-encoded representation as user[name] instead of just name. This looks a little funny to the client, but it means that Rails will parse a form-encoded representation and an ActiveRecord XML representation into the same data structure: one that looks like the one in Example 7-6.

The keys for the key-value pairs of a user account representation are user[name], user[password], user[full_name], and user[email]. Not coincidentally, these are the names of the corresponding fields in my database table users.

The keys for a representation of a bookmark are bookmark[short_description], bookmark[long_description], bookmark[timestamp], bookmark[public], and bookmark[tag][]. These are all the names of database fields, except for bookmark[tag][], which corresponds to a bookmark’s tags. I’ll be handling tags specially, and you might recall they’re kept in separate database tables. For now, just note that the extra “[]” in the variable name tells Rails to expect multiple tags in a single request.

Tip

There are other ways of allowing the client to specify multiple tags. The del.icio.us service itself represents a list of tags as a single tags variable containing a space-separated string. This is good for a simple case, but in general I don’t like that because it reimplements something you can already do with the form-encoded format.

A JSON data structure is another possible way of representing a bookmark. This would be a hash in which most keys correspond to strings, but where one key (tags) corresponds to a list.

The incoming representation of a tag contains only one key-value pair: the key is tag[name].

The incoming representation of a bundle contains two key-value pairs: bundle[name] and bundle[tag][]. The second one can show up multiple times in a single representation, since the point is to group multiple tags together. I’m approaching the implementation stage, so this is the last time I’ll mention bundles.

Design the Representation(s) Served to the Client

I’ve got a huge number of options for outgoing representation formats: think back to the discussion in Representing the List of Planets” in Chapter 5. Rails makes it easy to serve any number of representation formats, but the simplest to use is the XML representation you get when you call to_xml on an ActiveRecord object.

This is a very convenient format to serve from Rails, but it’s got a big problem: it’s not a hypermedia format. A client that gets the user representation in Example 7-4 knows enough to reconstruct the underlying row in the users table (minus the password). But that document says nothing about the relationship between that resource and other resources: the user’s bookmarks, tag vocabulary, or calendar. It doesn’t connect the “user” resource to any other resources. A service that serves only ActiveRecord XML documents isn’t well-connected.

I’m going to serve to_xml representations in a couple places, just to keep the size of this chapter down. I’ll represent a user account and a user’s tag vocabulary with to_xml. I’ll generate my own, custom to_xml-like document when representing a user’s posting history.

When I think about the problem domain, another representation format leaps out at me: the Atom syndication format. Many of the resources I’m exposing are lists of bookmarks: recent bookmarks, bookmarks for a user, bookmarks for a tag, and so on. Syndication formats were designed to display lists of links. What’s more, there are already lots of software packages that understand URIs and syndication formats. If I expose bookmark lists through a standard syndication format, I’ll immediately gain a huge new audience for my service. Any program that manipulates syndication feeds can take my resources as input. What’s more, syndication feeds can contain links. If a resource can be represented as a syndication feed, I can link it to other resources. My resources will form a web, not just an unrelated set.

My default representation will always be the to_xml one, but a client will be able to get an Atom representation of any list of bookmarks by tacking “.atom” onto the end of the appropriate URI. If a client GETs /users/leonardr/bookmarks/ruby, it’ll see a linkless to_xml representation of the bookmarks belonging to the user “leonardr” and tagged with “ruby.” The URI /users/leonardr/bookmarks/ruby.atom will give an Atom representation of the same resource, complete with links to related resources.

Connect Resources to Each Other

There are many, many relationships between my resources. Think about the relationship between a user and her bookmarks, between a bookmark and the tags it was posted under, or between a URI and the users who’ve bookmarked it. But a to_xml representation of a resource never links to the URI of another resource, so I can’t show those relationships in my representations. On the other hand, an Atom feed can contain links, and can capture relationships between resources.

Figure 7-1 shows my problem. When I think about the bookmarking service, I envision lots of conceptual links between the resources. But links only exist in the actual service when they’re embodied in representations. Atom representations contain lots of links, but to_xml documents don’t. To give one example, the conceptual link between a user and the user’s bookmarks doesn’t actually exist in my service. A client is just supposed to “know” how to get a user’s bookmarks.

The bookmarking service in my head versus the actual service
Figure 7-1. The bookmarking service in my head versus the actual service

Also note that while the “user” resource is clearly the focal point of the service, neither diagram gives any clue as to how a client can get to that resource in the first place. I’ve described that in English prose. That means that my real audience is the people writing the web service clients, not the clients themselves.

This is a failure of connectivity, and it’s the same failure you can see in Amazon S3 and some other RESTful services. As REST becomes more popular, this kind of failure will probably be the last remaining vestige of the RPC style. I dealt with this problem in Chapter 5 by defining a service home page that linked to a few top-level resources. These resources linked to more resources, and so on. My fantasy map application was completely connected.

What’s Supposed to Happen?

Rails exposes every database-backed application using only two resource patterns: lists (the database tables) and list items (the rows in a table). All list resources work pretty much the same way, as do all list item resources. Every “creation” operation follows the same rules and has similar failure conditions, whether the database row being created is a user, a bookmark, or something else. I can consider these rules as a sort of generic control flow, a set of general guidelines for implementing the HTTP interface for list and list item resources. I’ll start defining that control flow here, and pick it up again in Chapter 9.

When a resource is created, the response code should be 201 (“Created”) and the Location header should point the way to the resource’s location.

When a resource is modified, the response code should be 200 (“OK”). If the resource state changes in a way that changes the URI to the resource (for instance, a user account is renamed), the response code is 301 (“Moved Permanently”) and the Location header should provide the new URI.

When an object is deleted, the response code should be 200 (“OK”).

As far as possible, all resources that support GET should also support conditional GET. This means setting appropriate values for ETag and Last-Modified.

One final rule, a rule about data security. Unlike the del.icio.us API, I don’t require authentication just to get information from the service. However, I do have a rule that no one should see a user’s private bookmarks unless they’re authenticated as that user. If you look at someone else’s bookmarks, you’ll get a representation that has her private bookmarks filtered out. You won’t see the full resource state: just the part you’re authorized to see. This principle extends past the bookmark lists themselves, and into things like the calendar and tag vocabulary. You should not see mysterious tags showing up in the representation of my tag vocabulary, tags that don’t correspond to any of the tags I used in my visible bookmarks. This last rule is specific to my social bookmarking application, but its lessons can be applied more generally.

What Might Go Wrong?

The main problem is unauthorized access. I can use the 401 response code (“Unauthorized”) any time the client tries to do something (edit a user’s account, rename a tag for a user) without providing the proper Authorization header.

A client might try to create a user account that already exists. From the point of view of the service, this looks like an attempt to modify the existing account without providing any authorization. The response code of 401 (“Unauthorized”) is appropriate, but it might be confusing to the client. My service will send a 401 response code when the authorization is provided but incorrect, and a 409 (“Conflict”) when no authorization at all is provided. This way, a client who thought she was creating a new account is less likely to be confused.

Similarly, a client might try to rename a user account to a name that already exists. The 409 response code is appropriate here as well.

Any resource that’s a list of bookmarks will support query variables limit and date. These variables place restrictions on which bookmarks should show up in the representation: the client can set a maximum number of bookmarks to retrieve, or restrict the operation to bookmarks posted on a certain date. If the client sends a nonsensical limit or date, the appropriate response code is 400 (“Bad Request”). I’ll also use 400 when a user tries to create or modify a resource, but doesn’t provide a valid representation.

If the client tries to retrieve information about a nonexistent user, this service will do what del.icio.us does and send a response code of 404 (“Not Found”). This is the client’s cue to create that user account if they wish. I’ll do the same if the client tries to get information about a URI that no one has bookmarked.

A user can modify the URI listed in one of her bookmarks, but she can only have one bookmark for a given URI. If a user tries to change a bookmark’s URI to one she’s already bookmarked, a response code of 409 (“Conflict”) is appropriate. 409 is also the correct response if the user tries to POST a URI she’s already bookmarked. The uniform way to modify an existing bookmark is with PUT on the bookmark resource.

If the client tries to create a user account or bookmark, but provides an incomplete or nonsensical representation, the response is 400 (“Bad Request”). For instance, the client might try to POST a new bookmark, but forget to send the URI of the bookmark. Or it might try to bookmark a “URI” that’s not a URI at all.

When creating a user, the client might send a JSON representation of a new user, instead of an ActiveRecord XML or form-encoded representation of the same data. In other words, it might send the totally wrong media type. The proper response code here is 415 (“Unsupported Media Type”). Rails handles this failure condition automatically.

Controller Code

Now we come to the heart of the application: the code that converts incoming HTTP requests into specific actions on the database. I’m going to define a base class called ApplicationController, which contains common code, including almost all of the tricky code. Then I’ll define the six controller classes I promised earlier.

Each controller class will implement some actions: methods that are called to handle a HTTP request. Rails defines a list of standard actions that correspond to methods from HTTP’s uniform interface. I mentioned these earlier: the index action is invoked in response to GET for a “list” type resource, and so on. Those are the actions I’ll be defining, though many of them will delegate to other actions with nonstandard names.

There’s a lot of code in this application, but relatively little of it needs to be published in this book. Most of the low-level details are in Rails, the plugins, and the atom-tools gem. I can express my high-level ideas almost directly in code. Of course, my reliance on external code occasionally has downsides, like the fact that some of my representations don’t contain links.

What Rails Doesn’t Do

There’s one feature I want for my service that isn’t built into Rails or plugins, and there’s another that goes against Rails’s path of least resistance. I’m going to be implementing these features myself. These two items account for much of the tricky code in the service.

Conditional GET

Wherever possible, a web service should send the response headers Last-Modified and ETag along with a representation. If the client makes future requests for the same resource, it can make its requests conditional on the representation having changed since the last GET. This can save time and bandwidth; see Conditional GET” in Chapter 8 for more on this topic.

There are third-party Rails controllers that let the programmer provide values for Last-Modified and ETag. Core Rails doesn’t do this, and I don’t want to bring in the additional complexity of a third-party controller. I implement a fairly reusable solution for Last-Modified in Example 7-9.

param[:id] for things that aren’t IDs

Rails assumes that resources map to ActiveRecord objects. Specifically, it assumes that the URI to a “list item” resource identifies a row in a database table by ID. For instance, it assumes the client will request the URI /v1/users/4 instead of the more readable URI /v1/users/leonardr.

The client can still request /users/leonardr, and the controller can still handle it. This just means that the username will be available as params[:id] instead of something more descriptive, like params[:username].

If a URI contains more than one path variable, then when I define that URI in routes.rb I get to choose the params name for all but the last one. The last variable always gets put into params[:id], even if it’s not an ID. The URI /v1/users/leonardr/tags/food has two path variables, for example. params[:username], named back in Example 7-3, has a value of “leonardr”. The tag name is the one that gets put into params[:id]. I’d rather call it params[:tag], but there’s no good way to do that in Rails. When you see params[:id] in the code below, keep in mind that it’s never a database ID.

The ApplicationController

This class is the abstract superclass of my six controllers, and it contains most of the common functionality (the rest will go into the ActiveRecord model classes). Example 7-7 starts by defining an action for the single most common operation in this service: fetching a list of bookmarks that meet some criteria.

Example 7-7. app/controllers/application.rb
# app/controllers/application.rb
require 'digest/sha1'
require 'digest/md5'
require 'rubygems'
require 'atom/feed'

class ApplicationController < ActionController::Base

  # By default, show 50 bookmarks at a time.
  @@default_limit = 50

  ## Common actions

  # This action takes a list of SQL conditions, adds some additional
  # conditions like a date filter, and renders an appropriate list of
  # bookmarks. It's used by BookmarksController, RecentController,
  # and TagsController.
  def show_bookmarks(conditions, title, feed_uri, user=nil, tag=nil)
    errors = []

    # Make sure the specified limit is valid. If no limit is specified,
    # use the default.
    if params[:limit] && params[:limit].to_i < 0
      errors << "limit must be >=0"
    end
    params[:limit] ||= @@default_limit
    params.delete(:limit) if params[:limit] == 0  # 0 means "no limit"

    # If a date filter was specified, make sure it's a valid date.
    if params[:date]
      begin        
        params[:date] = Date.parse(params[:date])
      rescue ArgumentError
        errors << "incorrect date format"
      end
    end

    if errors.empty?
      conditions ||= [""]
      
      # Add a restriction by date if necessary.
      if params[:date]
        conditions[0] << " AND " unless conditions[0].empty?
        conditions[0] << "timestamp >= ? AND timestamp < ?"
        conditions << params[:date]
        conditions << params[:date] + 1
      end

      # Restrict the list to bookmarks visible to the authenticated user.
      Bookmark.only_visible_to!(conditions, @authenticated_user)

      # Find a set of bookmarks that matches the given conditions.
      bookmarks = Bookmark.custom_find(conditions, tag, params[:limit])
      
      # Render the bookmarks however the client requested.
      render_bookmarks(bookmarks, title, feed_uri, user)
    else
      render :text => errors.join("\n"), :status => "400 Bad Request"
    end
  end

The show_bookmarks method works like any Rails action: it gets query parameters like limit from params, and verifies them. Then it fetches some data from the database and renders it with a view. A lot of my RESTful action methods will delegate to this method. If the RESTful action specifies no conditions, show_bookmarks will fetch all the bookmarks that match the date and tag filters, up to the limit. Most of my actions will impose additional conditions, like only fetching bookmarks posted by a certain user.

The main difference between show_bookmarks and a traditional Rails action is in the view. Most Rails actions define the view with an ERb template like show.rhtml: a combination of HTML and Ruby code that works like JSP templates or PHP code. Instead, I’m passing the job off to the render_bookmarks function (see Example 7-8). This function uses code-based generators to build the XML and Atom documents that serve as representations for most of my application’s resources.

Example 7-8. application.rb continued: render_bookmarks
  # This method renders a list of bookmarks as a view in RSS, Atom, or
  # ActiveRecord XML format. It's called by show_bookmarks
  # above, which is used by three controllers. It's also used
  # separately by UriController and BookmarksController.
  # 
  # This view method supports conditional HTTP GET.
  def render_bookmarks(bookmarks, title, feed_uri, user, except=[])
    # Figure out a current value for the Last-Modified header.
    if bookmarks.empty?
      last_modified = nil
    else
      # Last-Modified is the most recent timestamp in the bookmark list.
      most_recent_bookmark = bookmarks.max do |b1,b2|
        b1.timestamp <=> b2.timestamp
      end
      last_modified = most_recent_bookmark.timestamp
    end
    
    # If the bookmark list has been modified since it was last requested...
    render_not_modified_or(last_modified) do
      respond_to do |format|
        # If the client requested XML, serialize the ActiveRecord
        # objects to XML. Include references to the tags in the
        # serialization.
        format.xml  { render :xml => 
          bookmarks.to_xml(:except => except + [:id, :user_id],
                           :include => [:tags]) }
        # If the client requested Atom, turn the ActiveRecord objects
        # into an Atom feed.
        format.atom { render :xml => atom_feed_for(bookmarks, title, 
                                                   feed_uri, user) }
      end
    end
  end

That method is also where I start handling conditional HTTP requests. I’ve chosen to use the timestamp of the most recent bookmark as the value of the HTTP header Last-Modified.

The rest of the conditional request handling is in the render_not_modified_or function (see Example 7-9). It’s called just before render_bookmarks is about to write the list of bookmarks, and it applies the rules of conditional HTTP GET. If the list of bookmarks has changed since this client last requested it, this function calls the Ruby keyword yield and the rest of the code in render_bookmarks runs normally. If the list of bookmarks hasn’t changed, this function short-circuits the Rails action, sending a response code of 304 (“Not Modified”) instead of serving the representation.

Example 7-9. application.rb continued: render_not_modified_or
  ## Helper methods

  # A wrapper for actions whose views support conditional HTTP GET.
  # If the given value for Last-Modified is after the incoming value
  # of If-Modified-Since, does nothing. If Last-Modified is before
  # If-Modified-Since, this method takes over the request and renders
  # a response code of 304 ("Not Modified").
  def render_not_modified_or(last_modified)
    response.headers['Last-Modified'] = last_modified.httpdate if last_modified

    if_modified_since = request.env['HTTP_IF_MODIFIED_SINCE']
    if if_modified_since && last_modified &&
        last_modified <= Time.httpdate(if_modified_since)
      # The representation has not changed since it was last requested.
      # Instead of processing the request normally, send a response
      # code of 304 ("Not Modified").
      render :nothing => true, :status => "304 Not Modified"
    else
      # The representation has changed since it was last requested.
      # Proceed with normal request processing.
      yield
    end
  end

Example 7-10 shows one more helper function used in multiple actions. The if_found method makes sure the client specified a URI that corresponds to an object in the database. If given a non-null object, nothing happens: if_found uses yield to return control to the action that called it. If given a null object, the function short-circuits the request with a response code of 404 (“Not Found”), and the action never gets a chance to run.

Example 7-10. application.rb continued: if_found.
  # A wrapper for actions which require the client to have named a
  # valid object. Sends a 404 response code if the client named a
  # nonexistent object. See the user_id_from_username filter for an
  # example.
  def if_found(obj)
    if obj
      yield     else
      render :text => "Not found.", :status => "404 Not Found"
      false
    end    
  end

I’ve also implemented a number of filters: pieces of code that run before the Rails actions do. Some Rails filters perform common setup tasks (see Example 7-11). This is the job of authenticate, which checks the client’s credentials. Filters may also check for a problem and short-circuit the request if they find one. This is the job of must_authenticate, and also must_specify_user, which depends on the if_found method defined above. Filters let me keep common code out of the individual actions.

Example 7-11. application.rb continued: filters
  ## Filters

  # All actions should try to authenticate a user, even those actions
  # that don't require authorization. This is so we can show an
  # authenticated user their own private bookmarks.
  before_filter :authenticate

  # Sets @authenticated_user if the user provides valid
  # credentials. This may be used to deny access or to customize the
  # view.
  def authenticate    
    @authenticated_user = nil
    authenticate_with_http_basic do |user, pass|      
      @authenticated_user = User.authenticated_user(user, pass)
    end
    return true
  end

  # A filter for actions that _require_ authentication. Unless the
  # client has authenticated as some user, takes over the request and
  # sends a response code of 401 ("Unauthorized").  Also responds with
  # a 401 if the user is trying to operate on some user other than
  # themselves. This prevents users from doing things like deleting
  # each others' accounts.
  def must_authenticate    
    if @authenticated_user && (@user_is_viewing_themselves != false)
      return true
    else
      request_http_basic_authentication("Social bookmarking service")      
      return false
    end    
  end

  # A filter for controllers beneath /users/{username}. Transforms 
  # {username} into a user ID. Sends a 404 response code if the user
  # doesn't exist.
  def must_specify_user
    if params[:username]
      @user = User.find_by_name(params[:username])      
      if_found(@user) { params[:user_id] = @user.id }
      return false unless @user
    end
    @user_is_viewing_themselves = (@authenticated_user == @user)
    return true
  end

Finally, the application controller is where I’ll implement my primary view method: atom_feed_for (see Example 7-12). This method turns a list of ActiveRecord Bookmark objects into an Atom document. The controller that wants to serve a list of bookmarks needs to provide a title for the feed (such as “Bookmarks for leonardr”) and a URI to the resource being represented. The resulting document is rich in links. Every bookmark links to the external URI, to other people who bookmarked that URI, and to bookmarks that share tags with this one.

Example 7-12. application.rb concluded: atom_feed_for
  ## Methods for generating a representation

  # This method converts an array of ActiveRecord's Bookmark objects
  # into an Atom feed.
  def atom_feed_for(bookmarks, title, feed_uri, user=nil)
    feed = Atom::Feed.new
    feed.title = title
    most_recent_bookmark = bookmarks.max do |b1,b2|
      b1.timestamp <=> b2.timestamp
    end
    feed.updated = most_recent_bookmark.timestamp

    # Link this feed to itself
    self_link = feed.links.new
    self_link['rel'] = 'self'
    self_link['href'] = feed_uri + ".atom"

    # If this list is a list of bookmarks from a single user, that user is
    # the author of the feed.
    if user
      user_to_atom_author(user, feed)
    end

    # Turn each bookmark in the list into an entry in the feed.
    bookmarks.each do |bookmark|
      entry = feed.entries.new
      entry.title = bookmark.short_description
      entry.content = bookmark.long_description

      # In a real application, a bookmark would have a separate
      # "modification date" field which was not under the control of
      # the user. This would also make the Last-Modified calculations
      # more accurate.
      entry.updated = bookmark.timestamp      

      # First, link this Atom entry to the external URI that the
      # bookmark tracks.
      external_uri = entry.links.new
      external_uri['href'] = bookmark.uri

      # Now we give some connectedness to this service. Link this Atom
      # entry to this service's resource for this bookmark.
      bookmark_resource = entry.links.new
      bookmark_resource['rel'] = "self"
      bookmark_resource['href'] = bookmark_url(bookmark.user.name, 
                                               bookmark.uri_hash) + ".atom"
      bookmark_resource['type'] = "application/xml+atom"

      # Then link this entry to the list of users who've bookmarked
      # this URI.
      other_users = entry.links.new
      other_users['rel'] = "related"
      other_users['href'] = uri_url(bookmark.uri_hash) + ".atom"
      other_users['type'] = "application/xml+atom"

      # Turn this entry's user into the "author" of this entry, unless
      # we already specified a user as the "author" of the entire
      # feed.
      unless user
        user_to_atom_author(bookmark.user, entry) 
      end

      # For each of this bookmark's tags...
      bookmark.tags.each do |tag|
        # ...represent the tag as an Atom category.
        category = entry.categories.new
        category['term'] = tag
        category['scheme'] = user_url(bookmark.user.name) + "/tags"

        # Link to this user's other bookmarks tagged using this tag.
        tag_uri = entry.links.new
        tag_uri['href'] = tag_url(bookmark.user.name, tag.name) + ".atom"
        tag_uri['rel'] = 'related'
        tag_uri['type'] = "application/xml+atom"

        # Also link to all bookmarks tagged with this tag.
        recent_tag_uri = entry.links.new
        recent_tag_uri['href'] = recent_url(tag.name) + ".atom"
        recent_tag_uri['rel'] = 'related'
        recent_tag_uri['type'] = "application/xml+atom"
      end
    end 
    return feed.to_xml
  end

  # Appends a representation of the given user to an Atom feed or element
  def user_to_atom_author(user, atom)
    author = atom.authors.new
    author.name = user.full_name
    author.email = user.email
    author.uri = user_url(user.name)
  end
end

Example 7-13 shows what kind of Atom representation this method might serve.

Example 7-13. An Atom representation of a list of bookmarks
<feed xmlns='http://www.w3.org/2005/Atom'>
 <title>Bookmarks for leonardr</title>
 <screen>
 <updated>2007-02-14T02:26:58-05:00</updated>
 <link href="http://localhost:3000/v1/users/leonardr/bookmarks.atom" rel="self"/>
 <author>
  <name>leonardr</name>
  <uri>http://localhost:3000/v1/users/leonardr</uri>
  <email>leonardr@example.com</email>
 </author>
 
 <entry>
  <title>REST and WS-*</title>
  <content>Joe Gregorio's lucid explanation of RESTful principles</content>
  <category term="rest" scheme="http://localhost:3000/v1/users/leonardr/rest"/>
  <link href="http://bitworking.org/news/125/REST-and-WS" rel="alternate"/>
  <link href="http://localhost:3000/v1/users/leonardr/bookmarks/68044f26e373de4a08ff343a7fa5f675.atom" 
   rel="self" type="application/xml+atom"/>
  ...
  <link href="http://localhost:3000/v1/recent/rest.atom"
   rel="related" type="application/xml+atom"/>
  <updated>2007-02-14T02:26:58-05:00</updated>
 </entry>
</feed>

The UsersController

Now I’m ready to show you some specific actions. I’ll start with the controller that makes user accounts possible. In the code in Example 7-14, note the call to before_filter that sets up the must_authenticate filter. You don’t need to authenticate to create (POST) a user account (as whom would you authenticate?), but you must authenticate to modify (PUT) or destroy (DELETE) an account.

Example 7-14. app/controllers/users_controller.rb
class UsersController < ApplicationController  

  # A client must authenticate to modify or delete a user account.
  before_filter :must_authenticate, :only => [:modify, :destroy]

  # POST /users
  def create   
    user = User.find_by_name(params[:user][:name])
    if user
      # The client tried to create a user that already exists.
      headers['Location'] = user_url(user.name)
      render :nothing => true, :status => "409 Conflict"
    else   
      user = User.new(params[:user])
      if user.save
        headers['Location'] = user_path(user.name)
        render :nothing => true, :status => "201 Created"
      else
        # There was a problem saving the user to the database.
        # Send the validation error messages along with a response
        # code of 400.
        render :xml => user.errors.to_xml, :status => "400 Bad Request"
      end
    end
  end

The conventions of RESTful Rails impose a certain structure on UsersController (and, indeed, on the name of the class itself). This controller exposes a resource for the list of users, and one resource for each particular user. The create method corresponds to a POST to the user list. The show, update, and delete methods correspond to a GET, PUT, or DELETE request on a particular user.

The create method follows a pattern I’ll use for POST requests throughout this service. If the client tries to create a user that already exists, the response code is 409 (“Conflict”). If the client sends bad or incomplete data, the ActiveRecord validation rules (defined in the User) model) fail, and the call to User#save returns false. The response code then is 400 (“Bad Request”). If all goes well, the response code is 201 (“Created”) and the Location header contains the URI of the newly created user. All I’ve done in Example 7-15 is put into code the things I said in What’s Supposed to Happen?” and What Might Go Wrong?” earlier in this chapter. I’ll mention this generic control flow again in Chapter 8.

Example 7-15. app/controllers/users_controller.rb continued
  # PUT /users/{username}
  def update
    old_name = params[:id]
    new_name = params[:user][:name]
    user = User.find_by_name(old_name)

    if_found user do
      if old_name != new_name && User.find_by_name(new_name)
        # The client tried to change this user's name to a name
        # that's already taken. Conflict!
        render :nothing => true, :status => "409 Conflict"      
      else        
        # Save the user to the database.
        user.update_attributes(params[:user])        
        if user.save
          # The user's name changed, which changed its URI.
          # Send the new URI.
          if user.name != old_name
            headers['Location'] = user_path(user.name)
            status = "301 Moved Permanently"
          else
            # The user resource stayed where it was.
            status = "200 OK"
          end
          render :nothing => true, :status => status
        else
          # There was a problem saving the user to the database.
          # Send the validation error messages along with a response
          # code of 400.
          render :xml => user.errors.to_xml, :status => "400 Bad Request"
        end
      end
    end
  end

The update method has a slightly different flow, and it’s a flow I’ll use for PUT requests throughout the service. The general outline is the same as for POST. The twist is that instead of trying to create a user (whose name might already be in use), the client can rename an existing user (and their new name might already be in use).

I send a 409 response code (“Conflict”) if the client proposes a new username that already exists, and a 400 response code (“Bad Request”) if the data validation fails. If the client successfully edits a user, I send not a 201 response code (“Created”) but a simple 200 (“OK”).

The exception is if the client successfully changes a user’s name. Now that resource is available under a different URI: say, /users/leonard instead of /users/leonardr. That means I need to send a response code of 301 (“Moved Permanently”) and put the user’s new URI in the Location header.

The GET and DELETE implementations are more straightforward, as shown in Example 7-16.

Example 7-16. app/controllers/users_controller.rb continued
  # GET /users/{username}
  def show
    # Find the user in the database.
    user = User.find_by_name(params[:id])
    if_found(user) do
      # Serialize the User object to XML with ActiveRecord's to_xml.
      # Don't include the user's ID or password when building the XML
      # document.
      render :xml => user.to_xml(:except => [:id, :password])
    end
  end

  # DELETE /users/{username}
  def destroy
    user = User.find_by_name(params[:id])
    if_found user do
      # Remove the user from the database.
      user.destroy
      render :nothing => true, :status => "200 OK"
    end
  end
end

There is one hidden detail: the if_found method sends a response code of 404 (“Not Found”) if the user tries to GET or DELETE a nonexistent user. Otherwise, the response code is 200 (“OK”). I have not implemented conditional HTTP GET for user resources: I figured the possible bandwidth savings wasn’t big enough to justify the added complexity.

The BookmarksController

This is the other main controller in this application (see Example 7-17). It exposes a user’s list of bookmarks and each individual bookmark. The filters are interesting here. This BookmarksController is for displaying a particular user’s bookmarks, and any attempt to see a nonexistent user’s bookmarks should be rebuffed with a stern 404 (“Not Found”). That’s the job of the must_specify_user filter I defined earlier. The must_authenticate filter works like it did in UsersController: it prevents unauthenticated requests from getting through to Rails actions that require authentication. I’ve also got a one-off filter, fix_params, that enforces consistency in incoming representations of bookmarks.

Example 7-17. app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController
  before_filter :must_specify_user
  before_filter :fix_params
  before_filter :must_authenticate, :only => [:create, :update, :destroy]

  # This filter cleans up incoming representations.
  def fix_params    
    if params[:bookmark]
      params[:bookmark][:user_id] = @user.id if @user
    end 
  end

The rest of BookmarksController is just like UsersController: fairly involved create (POST) and update (PUT) methods, simple show (GET) and delete (DELETE) methods (see Example 7-18). The only difference is that this controller’s list resource responds to GET, so I start with a simple implementation of index. Like many of the Rails actions I’ll define, index and show simply delegate to the show_bookmarks action.

Example 7-18. app/controllers/bookmarks_controller.rb continued
  # GET /users/{username}/bookmarks
  def index
    # Show this user's bookmarks by passing in an appropriate SQL
    # restriction to show_bookmarks.
    show_bookmarks(["user_id = ?", @user.id],
                   "Bookmarks for #{@user.name}", 
                   bookmark_url(@user.name), @user)
  end

  # POST /users/{username}/bookmarks
  def create
    bookmark = Bookmark.find_by_user_id_and_uri(params[:bookmark][:user_id], 
                                                params[:bookmark][:uri])
    if bookmark
      # This user has already bookmarked this URI. They should be
      # using PUT instead.
      headers['Location'] = bookmark_url(@user.name, bookmark.uri)
      render :nothing => true, :status => "409 Conflict"
    else
      # Enforce default values for 'timestamp' and 'public'
      params[:bookmark][:timestamp] ||= Time.now
      params[:bookmark][:public] ||= "1"

      # Create the bookmark in the database.
      bookmark = Bookmark.new(params[:bookmark])
      if bookmark.save
        # Add tags.
        bookmark.tag_with(params[:taglist]) if params[:taglist]

        # Send a 201 response code that points to the location of the
        # new bookmark.
        headers['Location'] = bookmark_url(@user.name, bookmark.uri)
        render :nothing => true, :status => "201 Created"
      else
        render :xml => bookmark.errors.to_xml, :status => "400 Bad Request"
      end
    end
  end

  # PUT /users/{username}/bookmarks/{URI-MD5}
  def update
    bookmark = Bookmark.find_by_user_id_and_uri_hash(@user.id, params[:id])
    if_found bookmark do
      old_uri = bookmark.uri
      if old_uri != params[:bookmark][:uri] && 
          Bookmark.find_by_user_id_and_uri(@user.id, params[:bookmark][:uri])
        # The user is trying to change the URI of this bookmark to a
        # URI that they've already bookmarked. Conflict!
        render :nothing => true, :status => "409 Conflict"
      else
        # Update the bookmark's row in the database.
        if bookmark.update_attributes(params[:bookmark])
          # Change the bookmark's tags.
          bookmark.tag_with(params[:taglist]) if params[:taglist]
          if bookmark.uri != old_uri
            # The bookmark changed URIs. Send the new URI.
            headers['Location'] = bookmark_url(@user.name, bookmark.uri)
            render :nothing => true, :status => "301 Moved Permanently"
          else
            # The bookmark stayed where it was.
            render :nothing => true, :status => "200 OK"
          end
        else
          render :xml => bookmark.errors.to_xml, :status => "400 Bad Request"
        end
      end
    end
  end

  # GET /users/{username}/bookmarks/{uri}
  def show
    # Look up the requested bookmark, and render it as a "list"
    # containing only one item.
    bookmark = Bookmark.find_by_user_id_and_uri_hash(@user.id, params[:id])
    if_found(bookmark) do
      render_bookmarks([bookmark], 
                       "#{@user.name} bookmarked #{bookmark.uri}",
                       bookmark_url(@user.name, bookmark.uri_hash),
                       @user)
    end
  end

  # DELETE /users/{username}/bookmarks/{uri}
  def destroy
    bookmark = Bookmark.find_by_user_id_and_uri_hash(@user.id, params[:id])
    if_found bookmark do
      bookmark.destroy
      render :nothing => true, :status => "200 OK"
    end
  end
end

The TagsController

This controller exposes a user’s tag vocabulary, and the list of bookmarks she’s filed under each tag (see Example 7-19). There are two twists here: the tag vocabulary and the “tag rename” operation.

The tag vocabulary is simply a list of a user’s tags, along with a count of how many times this user used the tag. I can get this data fairly easily with ActiveResource, and format it as a representation with to_xml, but what about security? If you tag two public and six private bookmarks with “ruby,” when I look at your tag vocabulary, I should only see “ruby” used twice. If you tag a bunch of private bookmarks with “possible-acquisition,” I shouldn’t see “possible-acquisition” in your vocabulary at all. On the other hand, when you’re viewing your own bookmarks, you should be able to see the complete totals. I use some custom SQL to count only the public tags when appropriate. Incidentally, this is another resource that doesn’t support conditional GET.

Example 7-19. app/controllers/tags_controller.rb
class TagsController < ApplicationController
  before_filter :must_specify_user
  before_filter :must_authenticate, :only => [:update]

  # GET /users/{username}/tags
  def index    
    # A user can see all of their own tags, but only tags used
    # in someone else's public bookmarks.
    if @user_is_viewing_themselves
      tag_restriction = ''
    else
      tag_restriction = " AND bookmarks.public='1'"
    end
    sql = ["SELECT tags.*, COUNT(bookmarks.id) as count" +
           " FROM tags, bookmarks, taggings" +
           " WHERE taggings.taggable_type = 'Bookmark'" +
           " AND tags.id = taggings.tag_id" +
           " AND taggings.taggable_id = bookmarks.id" + 
           " AND bookmarks.user_id = ?" + tag_restriction +
           " GROUP BY tags.name", @user.id]
    # Find a bunch of ActiveRecord Tag objects using custom SQL.
    tags = Tag.find_by_sql(sql)

    # Convert the Tag objects to an XML document.
    render :xml => tags.to_xml(:except => [:id])
  end

I said earlier I’d handle the “tag rename” operation with HTTP PUT. This makes sense since a rename is a change of state for an existing resource. The difference here is that this resource doesn’t correspond to a specific ActiveRecord object. There’s an ActiveRecord Tag object for every tag, but that object represents everyone’s use of a tag. This controller doesn’t expose tags, per se: it exposes a particular user’s tag vocabulary.

Renaming a Tag object would rename it for everybody on the site. But if you rename “good” to “bad,” then that should only affect your bookmarks. Any bookmarks I’ve tagged as “good” should stay “good.” The client is not changing the tag, just one user’s use of the tag.

From a RESTful perspective none of this matters. A resource’s state is changed with PUT, and that’s that. But the implementation is a bit tricky. What I need to do is find all the client’s bookmarks tagged with the given tag, strip off the old tag, and stick the new tag on. Unlike with users or bookmarks, I won’t be sending a 409 (“Conflict”) response code if the user renames an old tag to a tag that already exists. I’ll just merge the old tag into the new one (see Example 7-20).

Example 7-20. app/controllers/tags_controller.rb continued
  # PUT /users/{username}/tags/{tag} 
  # This PUT handler is a little trickier than others, because we
  # can't just rename a tag site-wide. Other users might be using the
  # same tag.  We need to find every bookmark where this user uses the
  # tag, strip the "old" name, and add the "new" name on.
  def update
    old_name = params[:id]
    new_name = params[:tag][:name] if params[:tag]
    if new_name
      # Find all this user's bookmarks tagged with the old name
      to_change = Bookmark.find(["bookmarks.user_id = ?", @user.id], 
                                old_name)
      # For each such bookmark...
      to_change.each do |bookmark|
        # Find its tags.
        tags = bookmark.tags.collect { |tag| tag.name }
        # Remove the old name.
        tags.delete(old_name)
        # Add the new name.
        tags << new_name
        # Assign the new set of tags to the bookmark.
        bookmark.tag_with tags.uniq
      end
      headers['Location'] = tag_url(@user.name, new_name)
      status = "301 Moved Permanently"
    end
    render :nothing => true, :status => status || "200 OK"
  end

  # GET /users/{username}/tags/{tag}
  def show
    # Show bookmarks that belong to this user and are tagged
    # with the given tag.
    tag = params[:id]
    show_bookmarks(["bookmarks.user_id = ?", @user.id], 
                   "#{@user.name}'s bookmarks tagged with '#{tag}'",
                   tag_url(@user.name, tag), @user, tag)
  end
end

The Lesser Controllers

Every other controller in my application is read-only. This means it implements at most index and show. Hopefully by now you get the idea behind the controllers and their action methods, so I’ll cover the rest of the controllers briefly.

The CalendarController

This resource, a user’s posting history, is something like the one exposed by TagsController#show. I’m getting some counts from the database and rendering them as XML. This document doesn’t directly correspond to any ActiveRecord object, or list of such objects; it’s just a summary. As before, I need to be sure not to include other peoples’ private bookmarks in the count.

The main body of code goes into the Bookmark.calendar method, defined in the Bookmark model class (see The Bookmark Model). The controller just renders the data.

ActiveRecord’s to_xml doesn’t do a good job on this particular data structure, so I’ve implemented my own view function: calendar_to_xml (see Example 7-21). It uses Builder::XmlMarkup (a Ruby utility that comes with Rails) to generate an XML document without writing much code.

Example 7-21. app/controllers/calendar_controller.rb
class CalendarController < ApplicationController
  before_filter :must_specify_user

  # GET /users/{username}/calendar
  def index
    calendar = Bookmark.calendar(@user.id, @user_is_viewing_themselves)
    render :xml => calendar_to_xml(calendar)
  end

  # GET /users/{username}/calendar/{tag}
  def show    
    tag = params[:id]
    calendar = Bookmark.calendar(@user.id, @user_is_viewing_themselves,
                                 tag)
    render :xml => calendar_to_xml(calendar, tag)
  end

  private

  # Build an XML document out of the data structure returned by the
  # Bookmark.calendar method.
  def calendar_to_xml(days, tag=nil)
    xml = Builder::XmlMarkup.new(:indent => 2)
    xml.instruct!
    # Build a 'calendar' element.
    xml.calendar(:tag => tag) do
      # For every day in the data structure...
      days.each do |day|
        # ...add a "day" element to the document
        xml.day(:date => day.date, :count => day.count)
      end
    end
  end
end

The RecentController

The controller in Example 7-22 shows recently posted bookmarks. Its actions are just thin wrappers around the show_bookmarks method defined in application.rb.

Example 7-22. app/controllers/recent_controller.rb
# recent_controller.rb
class RecentController < ApplicationController

  # GET /recent
  def index
    # Take bookmarks from the database without any special conditions.
    # They'll be ordered with the most recently-posted first.
    show_bookmarks(nil, "Recent bookmarks", recent_url)
  end

  # GET /recent/{tag}
  def show
    # The same as above, but only fetch bookmarks tagged with a
    # certain tag.
    tag = params[:id]
    show_bookmarks(nil, "Recent bookmarks tagged with '#{tag}'", 
                   recent_url(tag), nil, tag)
  end
end

The UrisController

The controller in Example 7-23 shows what the site’s users think of a particular URI. It shows a list of bookmarks, all for the same URI but from different people and with different tags and descriptions.

Example 7-23. app/controllers/uris_controller.rb
# uris_controller.rb
class UrisController < ApplicationController
  # GET /uris/{URI-MD5}
  def show
    # Fetch all the visible Bookmark objects that correspond to
    # different people bookmarking this URI.
    uri_hash = params[:id]
    sql = ["SELECT bookmarks.*, users.name as user from bookmarks, users" +
           " WHERE users.id = bookmarks.user_id AND bookmarks.uri_hash = ?",
           uri_hash]
    Bookmark.only_visible_to!(sql, @authenticated_user)
    bookmarks = Bookmark.find_by_sql(sql)

    if_found(bookmarks) do

      # Render the list of Bookmark objects as XML or a syndication feed,
      # depending on what the client requested.
      uri = bookmarks[0].uri
      render_bookmarks(bookmarks, "Users who've bookmarked #{uri}",
                       uri_url(uri_hash), nil)
    end
  end
end

Model Code

Those are the controllers. I’ve also got three “model” classes, corresponding to my three main database tables: User, Bookmark, and Tag. The Tag class is defined entirely through the acts_as_taggable Rails plugin, so I’ve only got to define User and Bookmark.

The model classes define validation rules for the database fields. If a client sends bad data (such as trying to create a user without specifying a name), the appropriate validation rule is triggered and the controller method sends the client a response code of 400 (“Bad Request”). The same model classes could be used in a conventional web application, or a GUI application. The validation errors would be displayed differently, but the same rules would always apply.

The model classes also define a few methods which work against the database. These methods are used by the controllers.

The User Model

This is the simpler of the two models (see Example 7-24). It has some validation rules, a one-to-many relationship with Bookmark objects, and a few methods (called by the controllers) for validating passwords.

Example 7-24. app/models/user.rb
class User < ActiveRecord::Base
  # A user has many bookmarks. When the user is destroyed,
  # all their bookmarks should also be destroyed.
  has_many :bookmarks, :dependent => :destroy

  # A user must have a unique username.
  validates_uniqueness_of :name

  # A user must have a username, full name, and email.
  validates_presence_of :name, :full_name, :email

  # Make sure passwords are never stored in plaintext, by running them
  # through a one-way hash as soon as possible.
  def password=(password)
    super(User.hashed(password))
  end

  # Given a username and password, returns a User object if the
  # password matches the hashed one on file. Otherwise, returns nil.
  def self.authenticated_user(username, pass)
    user = find_by_name(username)
    if user
      user = nil unless hashed(pass) == user.password
    end
    return user
  end

  # Performs a one-way hash of some data.
  def self.hashed(password)
    Digest::SHA1.new(password).to_s
  end
end

The Bookmark Model

This is a more complicated model (see Example 7-25). First, let’s define the relationships between Bookmark and the other model classes, along with some validation rules and a rule for generating the MD5 hash of a URI. We have to keep this information because the MD5 calculation only works in one direction. If a client requests /v1/uris/55020a5384313579a5f11e75c1818b89, we can’t reverse the MD5 calculation. We need to be able to look up a URI by its MD5 hash.

Example 7-25. app/models/bookmark.rb
class Bookmark < ActiveRecord::Base
  # Every bookmark belongs to some user.
  belongs_to :user

  # A bookmark can have tags. The relationships between bookmarks and
  # tags are managed by the acts_as_taggable plugin.
  acts_as_taggable

  # A bookmark must have an associated user ID, a URI, a short
  # description, and a timestamp.
  validates_presence_of :user_id, :uri, :short_description, :timestamp

  # The URI hash should never be changed directly: only when the URI
  # changes.
  attr_protected :uri_hash

  # And.. here's the code to update the URI hash when the URI changes.
  def uri=(new_uri)
    super
    self.uri_hash = Digest::MD5.hexdigest(new_uri)
  end

  # This method is triggered by Bookmark.new and by
  # Bookmark#update_attributes. It replaces a bookmark's current set
  # of tags with a new set.
  def tag_with(tags)
    Tag.transaction do
      taggings.destroy_all
      tags.each { |name| Tag.find_or_create_by_name(name).on(self) }
    end
  end

That last method makes it possible to associate tags with bookmarks. The acts_as_taggable plugin allows me to do basic queries like “what bookmarks are tagged with ‘ruby’?” Unfortunately, I usually need slightly more complex queries, like “what bookmarks belonging to leonardr are tagged with ‘ruby’?”, so I can’t use the plugin’s find_tagged_with method. I need to define my own method that attaches a tag restriction to some preexisting restriction like “bookmarks belonging to leonardr.”

This custom_find method is the workhorse of the whole service, since it’s called by the ApplicationController#show_bookmarks method, which is called by many of the RESTful Rails actions (see Example 7-26).

Example 7-26. app/models/bookmark.rb continued
  # This method finds bookmarks, possibly ones tagged with a
  # particular tag.
  def self.custom_find(conditions, tag=nil, limit=nil)
    if tag       
      # When a tag restriction is specified, we have to find bookmarks
      # the hard way: by constructing a SQL query that matches only
      # bookmarks tagged with the right tag.
      sql = ["SELECT bookmarks.* FROM bookmarks, tags, taggings" +
             " WHERE taggings.taggable_type = 'Bookmark'" +
             " AND bookmarks.id = taggings.taggable_id" +
             " AND taggings.tag_id = tags.id AND tags.name = ?",
             tag]
      if conditions
        sql[0] << " AND " << conditions[0]
        sql += conditions[1..conditions.size]
      end
      sql[0] << " ORDER BY bookmarks.timestamp DESC"
      sql[0] << " LIMIT " << limit.to_i.to_s if limit
      bookmarks = find_by_sql(sql)
    else
      # Without a tag restriction, we can find bookmarks the easy way:
      # with the superclass find() implementation.
      bookmarks = find(:all, {:conditions => conditions, :limit => limit,
                              :order => 'timestamp DESC'})
    end    
    return bookmarks
  end

There are two more database-related methods (see Example 7-27). The Bookmark.only_visible_to! method manipulates a set of ActiveRecord conditions so that they only apply to bookmarks the given user can see. The Bookmark.calendar method groups a user’s bookmarks by the date they were posted. This implementation may not work for you, since it uses a SQL function (DATE) that’s not available for all databases.

Example 7-27. app/models/bookmark.rb concluded
  # Restricts a bookmark query so that it only finds bookmarks visible
  # to the given user. This means public bookmarks, and the given
  # user's private bookmarks.
  def self.only_visible_to!(conditions, user)
    # The first element in the "conditions" array is a SQL WHERE
    # clause with variable substitutions. The subsequent elements are
    # the variables whose values will be substituted. For instance,
    # if "conditions" starts out empty: [""]...

    conditions[0] << " AND " unless conditions[0].empty?
    conditions[0] << "(public='1'"
    if user
      conditions[0] << " OR user_id=?"
      conditions << user.id
    end
    conditions[0] << ")"

    # ...its value might now be ["(public='1' or user_id=?)", 55].
    # ActiveRecord knows how to turn this into the SQL WHERE clause
    # "(public='1' or user_id=55)".
  end 

  # This method retrieves data for the CalendarController. It uses the
  # SQL DATE() function to group together entries made on a particular
  # day.
  def self.calendar(user_id, viewed_by_owner, tag=nil)    
    if tag
      tag_from = ", tags, taggings"
      tag_where = "AND taggings.taggable_type = 'Bookmark'" +
        " AND bookmarks.id = taggings.taggable_id" +
        " AND taggings.tag_id = tags.id AND tags.name = ?"
    end

    # Unless a user is viewing their own calendar, only count public
    # bookmarks.
    public_where = viewed_by_owner ? "" : "AND public='1'"

    sql = ["SELECT date(timestamp) AS date, count(bookmarks.id) AS count" +
           " FROM bookmarks#{tag_from} " +
           " WHERE user_id=? #{tag_where} #{public_where} " +
           " GROUP BY date(timestamp)", user_id]
    sql << tag if tag

    # This will return a list of rather bizarre ActiveRecord objects,
    # which CalendarController knows how to turn into an XML document.
    find_by_sql(sql)
  end
end

Now you should be ready to start your Rails server in a console window, and start using the web service.

$ script/server

What Does the Client Need to Know?

Of course, using the web service just means writing more code. Unlike a Rails service generated with script/generate scaffold (see Clients Made Transparent with ActiveResource” in Chapter 3), this service can’t be used as a web site. I didn’t create any HTML forms or HTML-based views of the data. This was done mainly for space reasons. Look back at Example 7-8 and the call to respond_to. It’s got a call to format.xml and a call to format.atom, and so on. That’s the sort of place I’d put a call to format.html, to render an ERb template as HTML.

Eventually the site will be well-populated with peoples’ bookmarks, and the site will expose many interesting resources as interlinked Atom representations. Any program, including today’s web browsers, can take these resources as input: the client just needs to speak HTTP GET and know what to do with a syndication file.

But how are those resources supposed to get on the site in the first place? The only existing general-purpose web service client is the web browser, and I haven’t provided any HTML forms for creating users or posting bookmarks. Even if I did, that would only take care of situations where the client is under the direct control of a human being.

Natural-Language Service Description

There are three possibilities for making it easy to write clients; they’re more or less the ones I covered in Chapters 2 and 3. The simplest is to publish an English description of the service’s layout. If someone wants to use my service they can study my description and write custom HTTP client code.

Most of today’s RESTful and hybrid web services work this way. Instead of specifying the levers of state in hypermedia, they specify the levers in regular media—English text—which a human must interpret ahead of time. You’ll need a basic natural-language description of your service anyway, to serve as advertisement. You want people to immediately see what your service does and want to use it.

I’ve already got a prose description of my social bookmarking service: it takes up much of this chapter. Example 7-28 is a simple command-line Ruby client for the service, based on that prose description. This client knows enough to create user accounts and post bookmarks.

Example 7-28. A rest-open-uri client for the bookmark service
#!/usr/bin/ruby
#open-uri-bookmark-client.rb
require 'rubygems'
require 'rest-open-uri'
require 'uri'
require 'cgi'

# An HTTP-based Ruby client for my social bookmarking service
class BookmarkClient

  def initialize(service_root)
    @service_root = service_root 
  end

  # Turn a Ruby hash into a form-encoded set of key-value pairs.
  def form_encoded(hash)
    encoded = []
    hash.each do |key, value|
      encoded << CGI.escape(key) + '=' + CGI.escape(value)
    end
    return encoded.join('&')
  end

  # Create a new user.
  def new_user(username, password, full_name, email)
    representation = form_encoded({ "user[name]" => username,
                                    "user[password]" => password,
                                    "user[full_name]" => full_name,
                                    "user[email]" => email })      
    puts representation
    begin
      response = open(@service_root + '/users', :method => :post, 
                      :body => representation)
      puts "User #{username} created at #{response.meta['location']}"
    rescue OpenURI::HTTPError => e
      response_code = e.io.status[0].to_i
      if response_code == 409 # Conflict
        puts "Sorry, there's already a user called #{username}."
      else
        raise e
      end
    end
  end

  # Post a new bookmark for the given user.
  def new_bookmark(username, password, uri, short_description)
    representation = form_encoded({ "bookmark[uri]" => uri,
                                    "bookmark[short_description]" => 
                                    short_description })
    begin
      dest = "#{@service_root}/users/#{URI.encode(username)}/bookmarks"
      response = open(dest, :method => :post, :body => representation,
                      :http_basic_authentication => [username, password])
      puts "Bookmark posted to #{response.meta['location']}"
    rescue OpenURI::HTTPError => e
      response_code = e.io.status[0].to_i
      if response_code == 401 # Unauthorized
        puts "It looks like you gave me a bad password."
      elsif response_code == 409 # Conflict
        puts "It looks like you already posted that bookmark."
      else
        raise e
      end
    end    
  end
end

# Main application
command = ARGV.shift
if ARGV.size != 4 || (command != "new-user" && command != "new-bookmark")
  puts "Usage: #{$0} new-user [username] [password] [full name] [email]"
  puts "Usage: #{$0} new-bookmark [username] [password]" +
    " [URI] [short description]"
  exit
end

client = BookmarkClient.new('http://localhost:3000/v1')
if command == "new-user"
  username, password, full_name, email = ARGV
  client.new_user(username, password, full_name, email)
else
  username, password, uri, short_description = ARGV
  client.new_bookmark(username, password, uri, short_description)
end

Description Through Standardization

One alternative to explaining everything is to make your service like other services. If all services exposed the same representation formats, and mapped URIs to resources in the same way... well, we can’t get rid of client programming altogether, but clients could work on a higher level than HTTP.[23]Conventions are powerful tools: in fact, they’re the same tools that REST uses. Every RESTful resource-oriented web service uses URIs to designate resources, and expresses operations in terms of HTTP’s uniform interface. The idea here is to apply higher-level conventions than REST’s, so that the client programmer doesn’t have to write as much code.

Take the Rails architecture as an example. Rails is good at gently imposing its design preferences on the programmer. The result is that most RESTful Rails services do the same kind of thing in the same way. At bottom, the job of almost every Rails service is to send and accept representations of ActiveRecord objects. These services all map URIs to Rails controllers, Rails controllers to resources, resources to ActiveRecord objects, and ActiveRecord objects to rows in the database. The representation formats are also standardized: either as XML documents like the one in Example 7-4, or form-encoded key-value pairs like the ones in Example 7-5. They’re not the best representation formats, because it’s difficult to make connected services out of them, but they’re OK.

The ActiveResource library, currently under development, is a client library that takes advantage of these similarities between Rails services. I first mentioned ActiveResource in Chapter 3, where I showed it in action against a very simple Rails service. It doesn’t replace custom client code, but it hides the details of HTTP access behind an interface that looks like ActiveRecord. The ActiveResource/ActiveRecord approach won’t work for all web services, or even all Rails web services. It doesn’t work very well on this service. But it’s not quite fair for me to judge ActiveResource by these standards, since it’s still in development. As of the time of writing, it’s more a promising possiblity than a real-world solution to a problem.

Hypermedia Descriptions

Even when the Ruby ActiveResource client is improved and officially released, it will be nothing more than the embodiment of some high-level design conventions. The conventions are useful: another web service framework might copy these conventions, and then Ruby’s ActiveResource client would work with it. An ActiveResource library written in another language will work with Rails services. But if a service doesn’t follow the conventions, ActiveResource can’t talk to it.

What we need is a general framework, a way for each individual service to tell the client about its resource design, its representation formats, and the links it provides between resources. That will give us some of the benefits of standardized conventions, without forcing all web services to comply with more than a few minimal requirements.

This brings us full circle to the REST notion of connectedness, of “hypermedia as the engine of application state.” I talk about connectedness so much because hypermedia links and forms are these machine-readable conventions for describing the differences between services. If your service only serves serialized data structures that show the current resource state, then of course you start thinking about additional standards and conventions. Your representations are only doing half a job.

We don’t think the human web needs these additional standards, because the human web serves documents full of links and forms, not serialized data structures that need extra interpretation. The links and forms on the human web tell our web browsers how to manipulate application and resource state, in response to our expressed desires. It doesn’t matter that every web site was designed by a different person, because the differences between them are represented in machine-readable format.

The XHTML links and forms in Chapters 5 and 6 are machine-readable descriptions of what makes the fantasy map service different from other services. In this chapter, the links embedded in the Atom documents are machine-readable descriptions of the connections that distinguish this service from others that serve Atom documents. In Chapter 9 I’ll consider three major hypermedia formats that can describe these differences between services: XHTML 4, XHTML 5, and WADL. For now, though, it’s time to take a step back and take a look at REST and the ROA as a whole.



[23] There will always be client-side code for translating the needs of the user into web service operations. The only exception is in a web browser, where the user is right there, guiding the client through every step.

Get RESTful Web Services now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.