[uf-rest] RESTful Rails plugin on RubyForge

Dan Kubb dan.kubb at autopilotmarketing.com
Tue Mar 14 01:45:38 PST 2006


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I'd like to announce the release of my RESTful Rails plugin on  
RubyForge.

It was previously discussed on this mailing list a couple of times  
before:

   http://microformats.org/discuss/mail/microformats-rest/2005- 
November/000042.html
   http://microformats.org/discuss/mail/microformats-rest/2006- 
February/000144.html

My first goal with this plugin is to attempt to try to show how
much HTTP kicks ass and address the framework shortcomings, not
already addressed by rails itself, that were outlined in the
excellent article "On HTTP Abuse":

   http://naeblis.cx/rtomayko/2005/04/22/on-http-abuse

This plugin adds some advanced support for HTTP's features to regular
rails controllers, like conditional GET/PUT/POST/DELETE, per-HTTP
method dispatch handing, transparent OPTIONS support and several others.

Installation of the plugin is pretty straight forward:

   1. Go to your rails application directory.

   2. If your rails application is under version control with Subversion
      run the command after "YES" below, otherwise run the command  
after "NO":

      YES:  script/plugin install -x svn://rubyforge.org/var/svn/ 
restful-rails/trunk
      NO:   svn checkout svn://rubyforge.org/var/svn/restful-rails/ 
trunk vendor/plugins/restful-rails

Thats it!

Documentation and Test Cases are forth-coming, but I wanted
to get this out for feedback now rather than wait any longer
while I continue to tweak it.

This is the third iteration after extracting this from a real
world project.  The API is still open for discussion and I would
love to see input what's there so far.

Since documentation is currently sparse, here's a sample
controller using per-method dispatching and conditional request
handling:

   class BookController < ApplicationController
     include RestController::Base

     def new
       conditions << @book = Book.new

       resource.get(:cache_as => :public, :for => 1.hour)
     end

     def collection
       conditions << @books = Book.find(:all)

       resource.post do
         @book = @books.build(params[:book])
         if @book.save
           render_post_success :action => 'by_id', :id => @book
         else
           render :action => 'new', :status => HTTP::Status::BAD_REQUEST
         end
       end
     end

     def by_id
       conditions << @book = Book.find(params[:id])

       resource.put do
         @book.attributes = params[:book]
         if @book.save
           render_put_success
         else
           render :status => HTTP::Status::BAD_REQUEST
         end
       end

       resource.delete do
         if @book.destroy
           render_delete_success :id => nil
         else
           render :status => HTTP::Status::BAD_REQUEST
         end
       end
     end
   end

There are only three things you need to do to use this plugin with
a normal controller:

   1. Include RestController::Base as I've done at the top of the
      example above.

   2. Append all the models that are used in the view for GET requests
      or modified by PUT/POST/DELETE requests to the conditions object
      as shown in the example above.

   3. Move the code that should only be executed in response to
      specific HTTP methods into individual blocks, and assign them
      the corresponding method handler in the resource object.

You can optionally also specify the caching instructions when
a method handler is executed, as shown in the new action's GET
handler.  In this case the Cache-Control and Expires headers
are set so that the response should be publicly cached for up
to 1 hour.

While the per-method dispatch is the most obvious new feature it
isn't the best part.  IMHO the best feature is the support for
Conditional HTTP requests.

When you append the models to the condition object, it will
construct ETag and Last-Modified headers if the models have
lock_version and updated_at attributes.  It will then compare the
If-* Request headers against these, performing a conditional
test to see if the per-method handler should execute and if the
view should be rendered.  If it can, the application will return
a 304 Not Modified or a 412 Preconditional Failed response.

Why is this cool?  Well, this solves the whole stale update
problem, especially for AJAX apps where we can control what
headers are sent.  Each time an AJAX app fetches a resource,
it could make note of the resource's Last-Modified and ETag
headers.  Later on when changing the same resource those headers
would be sent by the AJAX app in the If-Unmodified-Since and
If-Match headers respectively, along with the request.

Effectively this is saying to the server "Only perform the
operation if the copy of the server state matches what I
saw last time"...

That means Optimistic Locking for AJAX apps using plain old HTTP.

Another nice part of this is that if the browser is doing a
GET request, and the server state hasn't changed then the server
will simply return a 304 Not Modified response.  This skips the
rendering of the view, and almost no data is sent along the
wire to the browser.  The browser will just render what it had
cached locally, which as you can imagine is extremely fast all
around.

I would bet that apps that used conditional GET (when possible)
would scale better than those that did not.  It would be
interesting to test this though.

The best part is that support for Conditional GET is not just
limited to AJAX apps, the support in modern browsers is excellent.

So anyway, please install it and give it a spin.  Feedback is highly
appreciated.

- --

Thanks,

Dan
__________________________________________________________________

Dan Kubb
Autopilot Marketing Inc.

Email: dan.kubb at autopilotmarketing.com
Phone: 1 (604) 820-0212
Web:   http://autopilotmarketing.com/
vCard: http://autopilotmarketing.com/~dan.kubb/vcard
__________________________________________________________________



-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2.2 (Darwin)

iD8DBQFEFpDC4DfZD7OEWk0RArWXAJ0Sejk2pcCjZezyxHurJfITQjjMfwCgmcFx
9rb1jcGohgfKFvzjTQKdnt4=
=G67N
-----END PGP SIGNATURE-----


More information about the microformats-rest mailing list