Railsのresourcesが生成するURLがなんだか不自然だよ

DjangoでRESTfullな実装をしたいことがあり、とりあえずRailsのresourcesが生成するURLを真似して実装していました

ところが、途中でどうしても綺麗な実装ができない箇所があり、「Django不便だなー」なんて思ったのですが、よく考えるとRailsのresourcesが生成するURLが不自然じゃないかと思い始めました。

Railsが生成するURL

Railsに詳しくない人もいると思うので説明しておきます。config/routs.rb resource :item と記述すると、下のようなURLが生成されます。

URL HTTPメソッド 説明
/items GET 一覧
/items/:id GET 詳細
/items/:new GET 新規作成画面
/items POST 新規作成
/items/:id/edit GET 編集画面
/items/:id PUT 更新
/items/:id DELETE 削除

観察する

HTTPメソッドが違っても、同じURLの場合は処理する対象は同一であるべきと考えています。

それを念頭に置いて、先ほどのURLを見てみると、大まかには以下の3つに分けられます

  1. /items/:id (items/:id/editを含む)
  2. /items
  3. /items/new

この中で、1に関しては処理する対象は明確です。単一のリソースに対して表示、更新、削除を行います。

不自然なのは2です。/itemsにGETでアクセスすると'複数'のリソースが返されるのに対して、POSTでアクセスすると'単一'のリソースが生成されます。

ところで、URLの役割は忘れて構造だけを見ていると、/items/newはid=newであるような/items/:idであるとみなすことができることに気づけます。

  1. /items/:id
  2. /items

こうすると、各グループの役割が明確になってきました。1は単一のリソースに対する処理。2は複数のリソースに対する処理です。

再び生成されるURLを眺める

ここまでの考察をふまえて、もう一度生成されるURLを眺めてみます。

処理対象 URL HTTPメソッド
複数 /items GET
単一 /items POST
単一 /items/new GET
単一 /items/:id GET
単一 /items/:id/edit GET
単一 /items/:id PUT
単一 /items/:id DELETE

おかしな点が一目瞭然ですね!

一旦まとめ

不自然な点がわかったので、それを修正します、と言いたいところですが、一度言語化して整理しましょう。

1, resourcesが生成するURLは、単一のリソースに対する処理と複数のリソースに対する処理に分類できる。

2. /itemsは複数リソースに対する処理、/items/:idは単一のリソースに対する処理である(/items/newは、idがnewの/items/:idであると考えることができる)。

3. 以上の前提において、/itemsに対してPOSTリクエストを送信すると新しい'単一'リソースが生成されるのは不自然。

修正する

さて、これまでの流れをまとめたところで、いよいよ修正しましょう。

修正するべきはもちろん、/itemsにたいするPOSTが単一リソースの生成である点です。

/items/newにGETアクセスすると新規作成画面が取得できることを思い出してみてください。/items/newに対するアクセスは、まだidの確定していない(=新規の)リソースに対する操作であるとすると自然になりそうです。

よって、/ietms/newに対するPOSTのアクセスを新規作成に割り当てれば良さそうです。

補足

他の/items/:id同様にPUTを使用せずにPOSTを使用するのは、/items/newに対するPOSTは冪等でないからです。

冪等とは簡単に言うと、いつでも何度処理をしても同じ結果になる、ということです。

/items/:idにデータを送信すると、何度送信しても/items/:idが送信したデータの状態になる(=いつでも同じ結果になる)のに対し、/items/newにデータを送信するとその都度新しいリソースが作成されます。

修正結果

修正した結果、以下のようになりました。

処理対象 URL HTTPメソッド
複数 /items GET
単一 /items/new POST
単一 /items/new GET
単一 /items/:id GET
単一 /items/:id/edit GET
単一 /items/:id PUT
単一 /items/:id DELETE

当該の不自然さは解消されました。

その他

実は細かい点なら不自然なことは他にも幾つかあります。

例えば、/items/:idにGETリクエストを送信するとリソースの詳細が帰ってくるのに対して、/items/newだと新規作成画面が帰ってきます。

この新規作成画面は、すべての項目が空の編集画面(=新しいリソースの編集画面)と考えることができるため、/items/new/editに割り当てるのが自然であるように思えます。

「編集」という言葉に多くの人は、「既存のものを変更する」というイメージを抱くと思います。/items/new/editとすると新規リソースの編集ということになってしまい、「既存のものを変更する」というイメージと矛盾してしまいます。

よって、/ietms/new/editとするよりも/items/newとするほうが、他のURLとの統一感がなくなってしまいますが、直感的であると言えます。

まとめ

  • 最初はDjangoを疑ってしまったけれど、不自然だったのはRailsの生成するURLじゃない?

  • 好きなフレームワークなら有名なフレームワークと違っていても信じるべし

  • 有名だからって正しいとは限らないよね