リソースの部分更新 の草稿

だいぶ前に「次期RailsがPATCHメソッドを採用」という記事を書いて、

ただし、上記ではPATCH採用のもう1つの理由である「リソースの部分更新」あたりの説明をあえて省略しました。これはちょっと個人的に思うところがあるからです。

という感じなのですが、この詳細は次回に続くということで…。

と言ってそのまま放置していたのですが、途中まで書きかけのものをたまたま見つけたので、ついでに載せてみます。


前回の続きです。
前回省略したのですが、PUTの特徴的な性質は実はもう1つあります。

9.6 PUT

PUT メソッドは、同封されたエンティティを供給される Request-URI の元に保存するように要求する。 Request-URI が既に存在するリソースを参照している場合は、同封されるエンティティはオリジンサーバにあるそれの修正版とみなされるべきである。

http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec9.6

新たなメソッドは、相互互換性を改善し、エラーを排除するために必要である。既にPUTメソッドが、完全に新たな本体{body}をもってリソースを上書きするために定義されているが、このメソッドでは部分的な変更を行うために再利用することはできない。
(中略)
PUTリクエストでは、同封されたエンティティはオリジンサーバ上に保存されるリソースの修正バージョンであるとみなされ、クライアントは保存されるバージョンが置き換えられることを要求している。

http://www.studyinghttp.net/rfc_ja/rfc5789

RFC2616には明確に書いてあるわけではありませんが、その後のRFC5789ではPUTはリソースを「上書き」「置き換える」メソッドであると規定されています。ちょうどFTPなどでファイルをサーバに転送してそのまま保存するのと同じ感覚です*1

これに従うと、前回の例であるupdateアクションにcreated_at, updated_atのタイムスタンプを使用するのは、この意味でもまずいことになります。「置き換える」わけですから、PUTするデータにもcreated_atやupdated_atの内容が含まれていなければならないからです。

部分更新

もう1つの例として、invoice(送り状)リソースを考えます。このリソースには支払済みを表すためにpaidというフラグがあります。ある送り状を支払済みにするという操作は、従来のRailsではinvoiceのupdateですから、
PUT /invoices/:id
としてリクエストボディで paid=1 と送りたくなりますが、これも「置き換える」の規定に反しています。
子リソースを定義して
PUT /invoices/:id/paid
とすればOKですが、routes.rbに記述するのがめんどくさいですね。

それは現実的か?

やっぱりPATCHが正解だね、という結論なのですが、本当にそうでしょうか。

極端な例を出します。昔のホームページによくあった「アクセスカウンタ」です。今は廃れてしまった感がありますが、おかしな機能というわけではありません。
ユーザーページ /users/:id にどれぐらいのアクセスがあったかを数値で見せたいので、アクセス数の数値もリソースに含むことにします。GETすると数値が1増えるわけです。
すると、当然ですがGETするたび違うリソースが返ってくることになります。
前回引用したとおり、RFC2616にはGETも冪等と規定されています。これはまずいことになります。
こういう機能を実現するためには、これもPATCH(もしくはPOST)にしなければいけません。しかしそれでは、このURLに通常のリンクを張ることができなくなってしまいます。

これはたしかに極端な例でした。ではGETするたびに違う内容が返ってくるのはすべてダメかというと、そんなことはありません。
ものすごく単純ですが、「現在時刻リソース」を考えてみればわかります。これはGETするたびに違う内容を返しますが、「現在時刻」というリソースであることは変わりません。このGETは厳密な意味では冪等ではないのかもしれませんが、一般的には許容されていると思います。
これはPUTのタイムスタンプと大差ないと思いませんか?

部分更新も冪等

そこでinvoiceの例を振り返ってみると、
PUT /invoices/:id
で paid=1 を送ることは、完全に冪等です。実際これでほとんど問題ないと思います。
もし問題あるのであれば、
PUT /invoice/:id/paid
を簡単な記述で定義できるようにすればいいのでは?

「冪等」を捨てることのデメリット

全部のPUTをPATCHにしてしまうのは、「冪等」という制約を捨てることを意味します。前回挙げた冪等のメリットを捨ててしまうということです。
GET/POSTしかなかった世界に、RailsはなぜPUT/DELETEを導入したのでしょうか。PUTが外れるなら、PATCHじゃなくてPOSTでもいいのではないでしょうか。POSTも冪等ではないメソッドです。

一応conditional PATCHもあって冪等な形で適用することもできますが、ブラウザでは手動で制御することはできませんし、Railsがデフォルトでconditionalヘッダを発行するのはなかなか大変そうです。

PATCHによるdiff適用、インクリメントなど

(以下書けてません)

*1:ただし、RESTの名が示す通り、実際にやりとりされるのはリソースではなくリソースの表現なので、形式は変わってもよい