Rails Misapprehensions: Understanding RESTful PUT and POST
Friday, December 31st, 2010In the last post I talked about the rest in REST, 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?
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, 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).
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.
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.
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.
It’s all about entities…
I really had a hard time understanding where these semantics origin from until I read this fantastic summary (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.JPGdoesn’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.
…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/partyexpects 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/partyreplaces 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!
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!
