[uf-rest] Fwd: REST and Rails

Dan Kubb dan.kubb at autopilotmarketing.com
Fri Mar 31 14:18:05 PST 2006


-----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: 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)

iD8DBQFELaqd4DfZD7OEWk0RAvbHAJ0ZDZBiC+htf4ZuJfF6eIXvEMTT6gCfQPl4
GUHyt7lF6SEA7Oc1uvcjLxc=
=m2nK
-----END PGP SIGNATURE-----


More information about the microformats-rest mailing list