[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