Unless you’re not a Software Engineer, or you are but have spent the past 18 months on a different planet, you’ve probably heard all about how Rails 2.0 is RESTful. Now, before we get too far into what exactly “RESTful” means, let’s set a reference point: the only document I am aware of that nobody disputes — too much, anyway — is Roy Fielding’s dissertation on Representational State Transfer (REST). So, for the sake of brevity, let’s assume we don’t need to elaborate the finer points of the REST architectural style and just take Fielding’s work at face value.

Resources and Rails

REST basically states that messages between system components — like browsers and servers — are requests and responses that represent resources. That is to say, resources are “conceptual mappings”, but are not necessarily mapped to databases, models, or controllers. They can be mutable (like the page number that a book is currently opened to), and they can be singular or plural (book vs. books.) In a phrase, a resource is essentially a high level description of something at a particular point in time: the time at which your request is responded to. Some examples:

  • Your tab at the local pub
  • An investment portfolio
  • The contents of a public library
  • A book

As mentioned at the outset, Rails 2.0 supports REST. However, note that like most of Rails, the support is highly opinionated. The implementation is rather particular and expects that we have “drunk the Kool-Aid.” If we don’t approach it in this manner, we’ll probably miss out on the benefits REST on Rails has to offer.

Clean up your CRUD

Another concept of which I am certain you and I likely have a common understanding is Creating, Reading, Updating, and Destroying data, or CRUD for short. For anyone who has ever worked on a software system, these are the familiar building blocks of a pervasive and persistent data storage mechanism — it also happens to be what we get out of the Rails scaffold generator with a single command:

$ ./script/generate scaffold book title:string abstract:text isbn:string

And with that, we get a model, a controller with all the fixin’s (more on this in a moment), view templates, migration scripts, and — this is the wicked cool part — a RESTful CRUD implementation. Let’s create the database and spin up the server to check things out:

$ rake db:create
$ rake db:migrate
$ ./script/server

Now, point your browser to http://localhost:3000/books. Go ahead and play with the screens — create, read, update and delete books until your fingers hurt.

Keeping it simple

It’s so simple that it’s easy to forget how simple it is. With this (somewhat obtuse) statement, I am not referring to Rails or Ruby, but rather since we tend to approach operations involving databases in our applications through overly-complex abstractions and convoluted design patterns, it’s all too easy to forget just how uncomplicated CRUD really is. And this temporary amnesia often leads us to complicate code with misguided action names that obfuscate the actual database operation they perform. Consider an action named replace_book_title. See my point? Even though the controller is not obligated to map to the database the way the model is, things become far simpler — more conventional, one might say — when actions are named similarly or altogether after the CRUD operations they perform.

But what about routing? Even if we were so disciplined as to name all of our actions adhering to strict CRUD conventions, we could still screw the whole thing up by specifying stupid names for the routes — routes with names like add_book. (Remember when I said that Rails was opinionated?) And this is where Rails’ REST implementation saves us from doing stupid things…

map.resources :books

With this line in your routes.rb file, you get four named routes that allow you to call seven different controller actions — all with eerily CRUD-ish names like those I have been ranting about for the past 5 minutes. These actions are: index, show, new, create, edit, update, and destroy. To help get our heads wrapped around this concept, let’s look at all the routes:

$ rake routes
[output edited for brevity ...]
GET     /books               {:action=>"index", :controller=>"books"}
POST    /books               {:action=>"create", :controller=>"books"}
GET     /books/new           {:action=>"new", :controller=>"books"}
GET     /books/:id/edit      {:action=>"edit", :controller=>"books"}
GET     /books/:id           {:action=>"show", :controller=>"books"}
PUT     /books/:id           {:action=>"update", :controller=>"books"}
DELETE  /books/:id           {:action=>"destroy", :controller=>"books"}

Now, the first thing you’ll notice is that there is more in your output than I printed here. (I omitted the routes related to format — I’ll get to that a bit later.) The next thing you should notice is that in two cases (/books and /books/:id), different HTTP verbs combined with one URL path trigger different routes. For example, a PUT request to a URL of /books/1 will be mapped to the update action, but a DELETE request to the same URL will be mapped to the destroy action. So, basically, map.resources creates routes taking into account both the URL path and the HTTP verb combination. Pretty cool, eh?

But there’s an apparent problem: generally, browsers only issue POST and GET requests. So, how do we use the routes that are triggered by PUT and DELETE? Glad you asked.

The answer is form_for. If we put this in our new and edit forms …

<% form_for @book do |f| %>

the appropriate form tag will be generated based on the state of the @book object. So, if it’s a new book, we get:

<form action="/books" method="post">

Whereas if it’s an existing book, Rails figures out that this should be an update operation. As such, it generates:

<form action="/books/1" method="post">
  <input name="_method" type="hidden" value="put" />

(Or something similar.)

This gets me what?

While this is all fine and dandy, the RESTful new world we’re exploring has yet to offer us anything beyond some nifty shortcuts based on the tricking of unsuspecting web browsers. But let’s get back to those routes having to do with format

Assuming you still have some book records in your application database (if not, go create some) point your browser at http://localhost:3000/books.xml. Seeing the results should tip you off to the direction we’re about to go: managing our book objects via the RESTful XML API that we just realized we had. And got for free, I mind you.

But before we get too far, let’s take a look at the index action in our BooksController:

# GET /books
# GET /books.xml
def index
  @books = Book.find(:all)

  respond_to do |format|
    format.html # index.html.erb
    format.xml  { render :xml => @books }
  end
end

Of particular interest is the respond_to block and how it responds to requests for books with an XML format. And now, we can think back to the corresponding format route:

GET     /books.:format       {:action=>"index", :controller=>"books"}

This completes our tour of the plumbing. Now, let’s get on with the sexy stuff: creating a client program that remotely administers our book resources using XML.

Come together, right now

Put the following into book_client.rb:

require 'rubygems'
require 'activeresource'

class Book < ActiveResource::Base
  self.site = "http://localhost:3000"
end

books = Book.find(:all)

puts "The baseline list of books ..."
puts books.map(&:title)

b = Book.find(1)
b.title = "This title updated remotely ..."
b.save

puts "Updated a book ..."
books = Book.find(:all)
puts books.map(&:title)

b = Book.create(:title => "DESIGNING GREAT BEERS",
                 :abstract => "Essential reading for homebrewers.",
                 :isbn => "0-937381-50-0")

puts "Created a book ..."
books = Book.find(:all)
puts books.map(&:title)

b.destroy

puts "Destroyed a book ..."
books = Book.find(:all)
puts books.map(&:title)

And to run our client:

$ ruby book_client.rb

And that’s all there is to it. We now have a fully-functional RESTful application that supports both browsers and standalone XML clients.