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

Hi All,

Charlie Savage, Peter Williams and I were discussing RESTful
rails offlist and we though it might be a good idea to move
this onto the mailing list to get more people's input.

First to give you a little background.  The discussion was
sparked by a post Charlie made on his blog:

http://cfis.savagexi.com/articles/2006/03/26/rest-controller-for- rails

To which Peter replied on his blog:

http://pezra.barelyenough.org/blog/2006/03/another-rest-controller- for-rails/

I email Charlie offline that the three of us should discuss
RESTful Rails more.

The following is my reply to an email from Charlie on my RESTful
Rails Controller (http://microformats.org/discuss/mail/microformats- rest/2006-March/000158.html)

Begin forwarded message:

Hi Peter and Charlie,

Sorry about my delay in responding -- I'm planning a wedding
and my future bride had two days off, so I spent them with
her getting some details ironed out.

Okay, had a chance to look through this. The code snippet you include is very interesting:

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

I have to say its quite clever, I wouldn't have thought about it myself. First, let me make sure I understand the mechanics (please correct me if I make any mistakes).

* Using this approach, the url would be http://mysite/mycontroller/ collection and I could POST to it. Assumedly, I could also GET and PUT if I did this:

def collection

   resource.post do
   end

   resource.get do
   end
end

That's correct, although I'm kinda particular about
how my URI's look, so I'd probably use a route to
remove the word the "collection" from the URI.

As with normal rails actions, all resources respond
to get/head requests without you needing to specifically
define a handler for it.  The only reason in my framework
to do so is to specify the caching headers.

The jump shouldn't be that far for most rails developers
as its technically equivalent to:

def collection
  if request.post?
  end

  if request.get?
  end
end

There are four key differences though:

  1. It handles OPTIONS requests.  Each time resource.<method>
     is called it will "register" the method as allowed, and
     respond to the OPTIONS request by setting the proper
     Allow response header.

  2. If the request is for a method that isn't "registered",
     we return a 405 Method Not Allowed response.

  3. If the request is for a method not defined in RFC 2616
     or elsewhere, like WebDAV, we return a 501 Not Implemented.


  4. Before the method handler is executed it will check the
     "conditions" object, and compare it against the If-* request
     headers.  If the conditions on the server match what the
     client last saw, we return a 304 Not Modified in the
     case of GET/HEAD requests, or 412 Precondition Failed
     in the case of all other HTTP methods.

     The conditions object is basically just a collection of
     all the model objects that we're using in the view or
     to make logic decisions that affect what's placed in
     the view -- by view I mean representation in REST terms.

     In this way we have transparent Conditional request
     support.  Conditional requests allows you to do
     Optimistic Locking in your web service without having
     to resort to any "hacks".. I think this will be
     useful for AJAX apps too.

     Of course there's conditional GET support too which
     I think has the potential to give large speed increases
     in rails apps with minimal work.  Not only do you
     cut out the transfer time -- making the application
     perform faster from the client's POV -- but you also
     cut out rendering of the view on the server.  With
     some of my apps this is like 1/3 of the execution time.

* What template gets invoked - is it always collection.rhtml? I suppose thinking about this, in the end you only render templates for GETs so that's probably ok.

I've found this to be fine in every case I test.  Here's the
typical responses I use for key HTTP methods:

  GET    - 200 OK         - Response Body
POST - 201 Created - No Response Body, Location header to newly created resource
  PUT    - 204 No Content - No Response Body
  DELETE - 204 No Content - No Response Body

Even in the case of the PUT and DELETE if you returned
200 and the response body, you could return the same
representation as for a GET request so it would work out
well.

Now, I'm talking about Hi-REST here (oh how I hate that term,
but you both know what I mean, so its useful in disucssion).

For Lo-REST I'd do the following:  (assume I'm "tunneling" the
other methods over POST)

  GET    - 200 OK        - Response Body
POST - 303 See Other - No Response Body, Location header to newly created resource PUT - 303 See Other - No Response Body, Location header to resource DELETE - 303 See Other - No Response Body, Location header to collection resource

Now in the case of PUT you could also return 200 OK and just
return the same representation as a GET request to the
same URI.  I prefer to use 303 so that I don't get into
any weird issues where if someone hits refresh the same
request is sent .. although I guess in the case of a PUT
it wouldn't matter if it was sent again.

* How do you deal with editors - ie., pages that let you create new items or edit an existing one. Do you just fall back to Rails default new and edit methods?

I'm not sure I understand exactly what you mean by this..  I'll
try to answer with what I think you're asking, but let me know
if I misunderstood.

For a web service I wouldn't make any special contingency for
"edit" representations.  There would be one single resource and
you'd fetch the representation, change it, and then PUT it back
to the resource's URI.

Now web apps are different, since you can't style an HTML page
so that it can be used for both viewing and editing in a
cross-browser way; unless you wanted to rely on javascript
DOM rewriting, which I don't think is an option yet.  For a
web app here's how I'd normally lay it out:

  /book/1        - Viewable representation of book #1
  /book/1/editor - Editable representation of book #1

For a web service I'd PUT to /book/1.  With a web app, I'd
have the form on /book/1/editor tunnel a PUT over top of
POST to /book/1/editor, so that in the case of errors I
could bring up the same view.

I think its important to design for pure REST, and only
add on special cases to handle web browsers afterwards.

* How do you deal with filters?

There's nothing special that needs to be done with filters.  This
is a normal rails action, so all the same rules would apply.

Now onto a couple issues I see. First, I wonder if the approach above would be more confusing to other people than an approach based on the resource do end block. The problem with the approach above is that it looks just like an action, but behaves fairly different than an action (or standard Ruby methods for that matter). Maybe there would be value in just calling it out as a resource.

I don't see it as being that far removed from a normal action.  From
the developers point of view its not much different from a series of
if blocks or a case statement, except that it does a few extra things
behind the scenes which the developer doesn't have to worry about anyway.

Its not even using any special dispatching for the action.. It just
adds the "resource" method to the controller which executes a block
if the request method matches the "name" of the block.

In that case, what if we combined your ideas above with the resource do end block approach. Something like this:

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

   method.get 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

   method.post do      etc.
   end
 end

Is this a workable syntax in Ruby?

Sure it is, no problem.

Would "resource :collection" just create a normal rails action,
or do something different?

Not sure I like the name "method" though.  I'd rather see us
overload the request.get? method to execute a block if the
condition matches.. there are other ruby libraries that work
in this way: you have a method that evaluates to true/false,
and if you pass it a block it will execute that block when
it is true.

Here's two options to chew on:

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

    request.post? do
      @book = Book.new(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

OR #2

  resource :collection do |r|
    conditions << @books = Book.find(:all)

    r.post do
      @book = Book.new(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

I actually prefer the second option because I'm not sure
its a good idea to repurpose request.post? to execute
a block given the 4 special actions I outlined above
are being performed.

I guess method.get would have to evaluate to true to evalute the block. Have to go try that out... If it doesn't work, then you could model the respond_to do syntax in Rails 1.1 as you mentioned but that seems a bit like overkill to me.

I agree about overkill.  The respond_to is sort of a declarative
syntax, and its not really necessary in our case.. we can just execute
the code in-line when its reached, no need to defer execution until
later like with respond_to.

Last, for this solution, how would templates and filters work? If this would provide a solution to the method renaming I do now I'd be all for it.

They could work as before with no changes.

Second, I think the cache control functionality is really useful and should become a part of rails. However, I think it doesn't belong in the method definitions, just as I don't think that content types should be in method definitions either (like Peter did). Instead, I'd would prefer to see a Caching Rails plugin that would use some metaprogramming to work. Perhaps something like a filter?

cache :as => :public, :for => 1.hour
cache :as => :public, :for => 1.hour, only => [<resource_name or action>]

I think you're right about it not needing to be part of the method
definition.  The only thing we need to be aware of is that
you would rarely want to apply caching rules across an entire
resource.  More likely you'd want to apply caching on a
per-method basis.  Most people won't want to cache the response
to DELETE methods, but they will with GET methods.. Peter
said more about this in his reply, so I'll save some comments
when I reply to that email.

Of course, the only person's opinion that matters on this is David's. Did he give you any feedback on cache control?

He only said that he wanted a simpler syntax so that more people
would use caching properly.  IMHO the API in rails for caching
is sort of clumsy.

Hope this helps some...mind if I post parts of the discussion on my blog?

Go for it, the more discussion the better.

Not trying to steal any of your thunder for the XML.com article or anything... just more for letting others follow along with our discussion. Also, should we be having this conversation on the REST formats mailing list...hopefully would get some feedback from David (it would be nice to come up with something he likes so it gets into Rails someday)? Feel free to post your response there if you think its appropriate.

Do you guys want to repost these on the REST microformats
mailing list?  I'd be all for that.

- --

Thanks,

Dan
__________________________________________________________________

Dan Kubb
Autopilot Marketing Inc.

Email: [EMAIL PROTECTED]
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)

iD8DBQFELaqd4DfZD7OEWk0RAvbHAJ0ZDZBiC+htf4ZuJfF6eIXvEMTT6gCfQPl4
GUHyt7lF6SEA7Oc1uvcjLxc=
=m2nK
-----END PGP SIGNATURE-----
_______________________________________________
microformats-rest mailing list
[email protected]
http://microformats.org/mailman/listinfo/microformats-rest

Reply via email to