Rails Misapprehensions: Understanding RESTful PUT and POST

{{{
In the last post “I talked about the rest in REST”:http://nicksda.apotomo.de/2010/12/rails-misapprehensions-rest-is-more-than-get-post-put-and-delete/, beyond the four major verbs. Before getting completely wasted at the tonight’s new year party I’d like to quickly *illustrate my understanding of two HTTP verbs and how to think about them* in a RESTful mind.

While *GET and DELETE are pretty obvious*, a few points really bothered me with PUT and POST.

* What exactly is the *difference between POST and PUT*? In CRUD contexts, the first usually seems to be mapped to *_create_*, whereas the latter seems to be the *_update_* pendant. But that is not the end of the story!
* How do I *implement these verbs* in my Rails controllers? Is there some *standard, best practices*, or something?
* They say *PUT is _idempotent_* – that’s awesome, however, what the heck does that mean?

h3. Help! I’m idempotent!

Let’s explore *POST first, which is _not_ idempotent*. I wrote *a resource to categorize photos* I have on my hard-drive, where I simply can add photos to categories.

Here’s how I get *the textual representation of the _party_ category* (with “Restfulie”:https://github.com/caelum/restfulie, the one and only REST engine for Ruby).

res = Restfulie.at("http://localhost:3000/photos/party")
puts res.accepts("text/csv").get!

The category doesn’t contain too much pics, as I’m not partying enough:

IMG_0056.JPG, jpg
Michal_drinking.png, png

To *add another picture to the party resource*, sorry, party category, I could use *POST*.

res.as("application/jpg").post!(binary_jpg)

Don’t care about _how_ we POST and which format – *just notice _that_ we are POSTting to the party resource*. This results in another picture categorized in “party” (a polite controller would redirect us with a @201@ to the new photo resource).

h3. Multiple POSTs, duplicate content!

Now *if I do that 3 times in a row*, what happens?

3.times { res.post!(binary_jpg) }

We can *see the _non-idempotent_ effect of POST* right away.

IMG_0056.JPG, jpg
Michal_drinking.png, png
Nick-on-fire.png, png
Nick-on-fire_2.png, png
Nick-on-fire_3.png, png

The last embarrassing *picture was added three times*. Since we *repeatedly POSTed*, we have to take into account that things might be duplicated.

h3. If POST duplicates, can I simply use PUT?

Now, a common *deception is allowing users to PUT single images* to the @/photos/party@ resource. In the best case you’d simply *obfuscate the RESTfulness of your API*. In the worst case you’d *destroy your party picture collection* (depending how you wrote your serving controller)!

I’d like to *show how a correct PUT could look like*. After that, we discuss *_why_*.

h3. PUTting – done right!

Using the idempotent nature of PUT I should be able to *insert the new photo only once, even if I PUT repetitively*. Here’s the _client_ code.

res= Restfulie.at("http://localhost:3000/photos/party").
  accepts("application/zip")

pics = res.get!
pics.unzip!
pics << binary_jpg # add the new photo

# and PUT it back:
res.put!(pics.zip!)

Does that look confusing? I guess it does. What am I doing here?

# I *retrieve all photos* categorized in “party” as a ZIP file
# then I *unzip and add* the new photo (for simplicity I simply assume there are methods like @#zip!@ and @#unzip!@ around)
# last step is *PUTting back the grown ZIP archive*

That’s how PUT is intended to work. Let’s discuss.

h3. It’s all about entities…

I really had a hard time understanding where these semantics origin from until I read “this fantastic summary”:http://www.markbaker.ca/2002/08/HowContainersWork/ (any cite following from this page).

In RESTful architectures we have to *distinguish between _entities_ and _container_ resources.*

A *typical _entity_ is the photo resource*.

* *POSTing to @/photos/IMG_0056.JPG@ doesn’t make sense* here – should we append another picture to the existing? No, non-sense.
* However, it is absolutely conclusive that a *PUT would overwrite the original picture*.

h3. …and containers.

Our “party pictures” resource is a stereotypical _container_ resource.

* as _”POST’s job is simply to […] add something to a container”_ *it is obvious that POSTing to @/photos/party@ expects a new image* binary (or whatever) which is added to the party category.
* _”If you PUT to a container, you are attempting to overwrite the state that was constructed from all the previous POSTs to it”_ – I couldn’t find better words to describe that. A *PUT to @/photos/party@ replaces the existing collection* with the one you send.

With that in mind, it should be *pain if you allow people to PUT single images* to @/photos/party@ – since *your controller (should!) expects a complete image collection!*

h3. Conclusion

Think of *POST as in _adding a new entity_* (often to a container). When you hear PUT, keep in mind that *PUT is more like _replacing the addressed resource entirely_ with the new data you send*.

*How to do that in Rails* is worth another post – in 2011. Cheers and Happy New Year!
}}}

Advertisements

7 thoughts on “Rails Misapprehensions: Understanding RESTful PUT and POST

  1. You would want to check out the PATCH verb. In theory PUT is an idempotent verb that should be used to “replace” the previous resource version. Rails use it with “update_attributes” which allow for partial updates, but that is what PATCH should do. PUT should send the changing bits and all the other attributes as well to have a complete replacement document. Check out my RailsConf talk “Making Rails really Restful” at http://slideshare.net/akitaonrails about these concepts and Restfulie.

    Like

  2. By the way I think PATCH is still not currently supported by Rack. I did a pull request to the Rack project months ago but I think it was declined (have to check it out again). That would be the first step towards making a real restful Rails.

    Like

  3. It’s nice to finally see some focus on actual REST. A good way to think about it is that with PUT you name the resource you’re working with (adding/replacing) whereas POST identifies a resource that will do some kind of processing and possibly produce a new resource.

    Like

  4. Very interesting about the difference between PUT and PATCH. I always thought it a bit weird that PUT (by rails conventions at least) didn’t return an updated serialization of the updated resource. But rather just says 200 with nothing else in the response.

    I guess it makes more sense if we actually expected the resource to be replaced with whatever we sent, meaning we would already have the complete state of the object on the client side.

    Does PATCH have another way of responding than PUT?

    Like

  5. Hi Nick and Akita. Great post.

    I believe when including the Restfulie gem it automatically adds support to Rails to the patch verb on the server side (until Rack+Rails dont, its the work around).
    The client side APIs are tipically implemented using inheritance on their types (i.e. POST < GET) and so the Restfulie client api also patches Net:HTTP to support it.

    Putting x Posting is indeed an important subject and it would be great to see APIs using http according to its specs (and thus becoming one step closer to better integrating over than web than today’s web services 🙂

    Regards

    Like

  6. Fantastic writeup. In the middle of defining an API, I’ve been googling around for “PUT vs POST” and came across a handful of blog posts that simply quoted the RFC (which didn’t make it anymore clear).

    Your examples and humor made this much easier to grok; thanks!

    Like

  7. Here’s an interesting quote from the RESTful Web Services book (p. 99): the client uses PUT when it’s in charge of deciding which URI the new resource
    should have. The client uses POST when the server is in charge of deciding which URI
    the new resource should have.

    So, to add another image we could PUT it to /photos/parties/drink-it-down.jpg.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s