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
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
$ rake db:migrate