Menu

REST on Rails

November 2, 2005

Matt Biddulph

Imagine a news website with simple URLs for its news items and the categories they belong to: http://example.com/item/15 and http://example.com/category/rails. Suppose that it has been built using Ruby on Rails. In the article, we'll discuss how to REST-enable this site, giving read-write HTTP access to machine-readable representations of its resources. By the end of the article, we'll be able to add these features to our site in just a couple of lines of code.

If you're not yet familiar with the Rails framework, I suggest you introduce yourself to it via an article like "What Is Ruby on Rails" before reading on. You won't regret it. For more background on the REST architectural style, Joe Gregorio's series of XML.com articles "The Restful Web" are very useful.

(rest_resource.rb contains all the code excerpted in this article.)

Mapping REST Concepts to Rails Patterns

Following the Model/View/Controller pattern, our site's resources will be represented by Model classes, in this case, an Item and a Category class. In REST terminology, these are resources, URL-addressable entities that you can interact with over HTTP. Views are a way to create REST's representations, useful serializations of the models that clients can interpret. We decide which representation of which resource to use in dispatch, a role played by the Controller.

HTTP's standard PUT/GET/POST/DELETE verbs translate directly to the Create/Read/Update/Delete features built into every Rails ActiveRecord model. This means no changes or additions for existing model classes to be REST-enabled -- as is usual in the MVC framework, models don't have to know or care what they're being used for.

We'll provide a view that does an automatic mapping from any Rails model to a default XML representation. We'll build it with a template using ActiveRecord introspection to discover the schema and record values at runtime. The XML format will look something like this:

<nameofmodelclass>

  <field1>field 1's value</field1>

  <field2>field 2's value</field2>

  <onetomanyfield1

href='http://example.comm/url/of/relatedrecord1' />

  <onetomanyfield1

href='http://example.comm/url/of/relatedrecord2' />

</nameofmodelclass>

When a client POSTs such an XML document to the URL of an existing resource, we'll parse it and update the values of the record in the database. Clients will also be able to create new resources by POSTing XML to a create URL and being redirected to the newly created resource. This logic will be implemented in the Controller.

Dispatch with ActionController

To handle incoming requests to create, read, update or delete resources, we need to inspect the incoming request before we can take action. Rails handles most of the hard work in decoding a URL and calling the right controller method, but it doesn't differentiate between different HTTP verbs. We'll have to make this choice in our controller method. ActionController provides a bunch of convenient Boolean methods for this using different HTTP verbs, so the skeleton of our code looks like this:

if request.post?

  if params[:id]

    # it's an update

  else

    # no ID supplied, so create new resource

  end

end

if request.get?

  # serve up a representation

end

if request.delete?

  # delete the resource

end

You may like to compare this dispatch mechanism to the Python code in Joe Gregorio's Dispatching in a REST Protocol Application.

Using REXML and Builder

To implement the XML handling for this application, we're going to need a way of consuming XML, and a way of producing it. Fortunately every Rails installation ships with REXML and Builder.

Builder is the standard Rails mechanism for producing XML. If you're familiar with .rhtml files used for HTML templating then making the requisite .rxml files won't be a great leap, and will integrate with your application's controllers in just the same way. Here's a simple example of producing some XML:

xml.instruct! :xml, :version=>"1.0",

:encoding=>"UTF-8"

xml.example('version' => '2.0') do

  xml.foo("This is the title")

end

The template produces this output:

<?xml version="1.0"

encoding="UTF-8"?>

<example>

  <foo>This is the title</foo>

</example>

Ruby ships with REXML, which incorporates both a parser and an XPath implementation. You can parse an XML document like this:

doc = REXML::Document.new(resp.body)

and extract data with XPath like so:

REXML::XPath.each(doc,"/example/foo") { |xml|

  puts xml.text

}

Introspection with ActiveRecord

Let's take a break from XML and look at how we can dynamically get data from any model object without prior knowledge of its fields. To understand fully how this works, I highly recommend firing up the Rails console (run script/console from the command line) on any Rails project and trying out the commands interactively. We'll show excerpts from an interactive session as we go through the concepts.

Every model is a subclass of ActiveRecord::Base, and this brings with it a number of essential methods for our task. The first is content_columns:

>> i = Item.find_first()

=> #<Item:0x2333814 @attributes={"title"=>"This is

an article's title", "id"=>"1", "bodytext"=>"The text of

the item"}>



>> i.class.content_columns

=> [#<ActiveRecord::ConnectionAdapters::Column:0x2331bcc

@type=:string, @default=nil, @limit=100, @name="title">,

#<ActiveRecord::ConnectionAdapters::Column:0x2331adc

@type=:string, @default=nil, @limit=250, @name="bodytext">



>> i['title']

=> "This is an article's title"

Notice that content_columns is a class method, so we invoke it as i.class.content_columns. It tells us that Items have two content columns of type string. We can loop through this list of column names in our XML template to get their values.

What if the model has associations? ActiveRecord has powerful automatic handling of database joins to manage relationships between models. These relationships are a key part of most Rails apps. ActiveRecord provides another useful class method:

>>

i.class.reflect_on_all_associations

=>

[#<ActiveRecord::Reflection::AssociationReflection:0x2337860

@macro=:belongs_to, @options={}, @name=:category,

@active_record=Category>]

This tells us that our Item has one association: it belongs to a Category. To find out what category our item belongs to, we just use one line of Ruby to resolve and call the category method given there, and we have all the data we need to make a serialization:

>> i.method('category').call

=> #<Category:0x22fb2e8

@attributes={"description"=>"Technical articles",

"id"=>"2" }>

Putting it all together, the RXML template looks like this:

xml.tag!(@obj.class.to_s.downcase,{:id =>

@obj.id}) {

    @obj.class.content_columns.each { |col|

       

xml.tag!(col.name,@obj[col.name])

    }

   

@obj.class.reflect_on_all_associations.each { |assoc|

       

if assoc.macro == :belongs_to || assoc.macro == :has_one

           

rels = [@obj.method(assoc.name).call]

       

end

       

if assoc.macro == :has_many || assoc.macro == :has_and_belongs_to_many

            #

*_many methods return lists

           

rels = @obj.method(assoc.name).call

       

end

       

rels.each { |rel|

           

if rel

               

name = rel.class.to_s.downcase

               

xml.tag!(name,{:id=>rel.id, :href

=>url_for(:only_path=>false,:action =>

name+"_xml",:id=>rel.id)})

           

end

        }

    }

}

Using this template and a little REXML XPath code as above, we can complete the code for dispatch in the controller.

In Action

Let's see a command-line session with our REST-enabled web app. We'll use the REST hacker's number one web services client: /usr/bin/curl.

First create a new Item by POSTing some XML to the app:

$ curl -i -X POST -d

"<item><title>Article

title</title><bodytext>no

body</bodytext></item>"

http://localhost:3000/blog/item    





HTTP/1.1 201 Created

Date: Sun, 09 Oct 2005 13:12:54 GMT

Location: http://localhost:3000/blog/item/1137975

The server parsed our XML, created an Item model instance, initialised it with our data and stored it in the database. It indicated success with the HTTP code 201 Created, and told us where to find the new resource with the Location header.

Now request a representation of the resource from its newly-minted URL:

$ curl http://localhost:3000/blog/item/1137975

<item id="1137975">

  <title>Article title</title>

  <bodytext>no body</bodytext>

</item>

The XML comes back as expected. Make a change to the resource by posting data to its URL:

$ curl -i -X POST -d

"<item><title>New

title</title></item>"

http://localhost:3000/blog/item/1137975



HTTP/1.1 200 OK

Date: Sun, 09 Oct 2005 13:14:29 GMT

Content-Type: text/xml



<item id="1137975">

  <title>New title</title>

  <bodytext>no body</bodytext>

</item>

Note that the title has changed but the bodytext field remains untouched as we didn't specify it in our input.

Finally, delete the resource:

$ curl -i -X

DELETE http://localhost:3000/blog/item/1137975



HTTP/1.1 204 Deleted

Date: Sun, 09 Oct 2005 13:14:29 GMT

Content-Type: text/xml

And verify that it's gone:

$ curl -i http://localhost:3000/blog/item/1137975



HTTP/1.1 404 Not Found

Date: Sun, 09 Oct 2005 13:17:29 GMT

Let's Make it a One-Liner

Now we have our code written, wouldn't it be nice if we could apply it to any model in any rails project? Let's package up the code so that it can be pulled in with one line of controller code, just like Rails builtins such as belongs_to, validates_uniqueness_of and layout.

To do this, we exploit Ruby's dynamic nature by providing a mixin class that transparently adds features to an existing class. There's a fairly simple code pattern for doing this:

module ExtraMethods

    def self.append_features(base)

       

super

       

base.extend(ClassMethods)

    end

    module ClassMethods

        def

additional_method(message)

         

module_eval <<-"end_eval",__FILE__,__LINE__

         

  def example

         

    puts "I've been added to give you this message:

'#{message}'"

         

  end

          end_eval

        end

    end

end

We can now use this in any class with a call to include:

class SomeClass

    include ExtraMethods

    additional_method "your dynamic method insertion

worked"

end

>> SomeClass.new().example()

I've been added to give you this message: 'your dynamic method

insertion worked

You can see this Ruby pattern in use in the code that accompanies this article. With the final code present in the lib directory of the project, a REST-enabled controller looks like this:

class NewsController << ApplicationController

    rest_resource :item

    rest_resource :category

end

Conclusion

Rails is a strong foundation for the REST style. Its approach to database modeling and URL routing leads to good URLs representing resources. These are easy to enable for XML reading and writing using simple dispatch mechanisms. Ruby's dynamic language features make it possible to package up convenience code into an easy-to-use one-liner.

In any application where the client can make changes to your database, you have to think carefully about granting access rights. The code presented here could be enhanced to use a Rails-based authentication system, or alternatively a web server such as Apache can be used to front an application and grant fine-grained rights dependent on user and HTTP verb.

It's instructive to compare the style of interaction in this article with the emerging Atom API standard, and to think about how this kind of API lends itself well to embedding in all sorts of clients, from command utilities to OSX Dashboard widgets, to Web 2.0 mashups.