2.0のPUT問題をRoutes設定で解決する

前回のlink_to_remoteでリクエストがPUTメソッドとして扱われてしまい、常にupdateアクション呼び出しになってしまう現象への対策は、link_to_remoteの:submitオプションで送信範囲を限定してしまうという、後ろ向きな対策だった。このような対策をしてしまうのは、自分がRoutes設定に疎い証拠だ...。この方法だと、form_forブロックを含めた範囲を送信する必要がある状況では、どうするべきなのか?という問題が残ってしまう。もっと前向きな対策が必要だ。

Routes設定で解決する

結論から書けば、以下のように設定しておくと:submitの範囲にform_forが含まれていても、挿入・削除・コピーが正常に機能する。(insert_row、delete_row、copy_rowアクションが呼び出される。)

  • :member=>[:insert_row, :copy_row, :delete_row]を追記した。
map.resources :slips, :member=>[:insert_row, :delete_row, :copy_row]

map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

何が起こっているのか?

設定されるRoutes
  • 上記Routes設定で生成されるルート規則をrake routesで確認してみた。(見づらいので、ペアで生成される:formatルートは省略した。)
#           名前付きルート名 メソッド  URLパス書式                       処理されるコントローラー、アクション
#-----------------------------------------------------------------------------------------------------------
                    slips GET    /slips                           {:controller=>"slips", :action=>"index"}
                          POST   /slips                           {:controller=>"slips", :action=>"create"}
                 new_slip GET    /slips/new                       {:controller=>"slips", :action=>"new"}
          insert_row_slip        /slips/:id/insert_row            {:controller=>"slips", :action=>"insert_row"}
            copy_row_slip        /slips/:id/copy_row              {:controller=>"slips", :action=>"copy_row"}
          delete_row_slip        /slips/:id/delete_row            {:controller=>"slips", :action=>"delete_row"}
                edit_slip GET    /slips/:id/edit                  {:controller=>"slips", :action=>"edit"}
                     slip GET    /slips/:id                       {:controller=>"slips", :action=>"show"}
                          PUT    /slips/:id                       {:controller=>"slips", :action=>"update"}
                          DELETE /slips/:id                       {:controller=>"slips", :action=>"destroy"}
                                 /:controller/:action/:id         
                                 /:controller/:action/:id.:format 
  • オレンジ色の部分が:member=>[:insert_row, :delete_row, :copy_row]を追記したことにより、新たに追加されたRoutes。
解読する

1行目を例に何が書いてあるか、自分勝手な訳。

#           名前付きルート名 メソッド  URLパス書式                       処理されるコントローラー、アクション
#-----------------------------------------------------------------------------------------------------------
                    slips GET    /slips                           {:controller=>"slips", :action=>"index"}
  • メソッドがGETで、URLがhttp://localhost:3000*1/slipsという書式のHTTPリクエストを受け付けたら...
    • slipsコントローラーの、indexアクションを実行する。
    • 上記コントローラー、アクションを指定する時には、slips_url、またはslips_pathが利用できる。
slips_url  = "http://localhost:3000/slips"
slips_path = "/slips"
    • slips_pathの利用例
<%= link_to 'Index', slips_path %>
:member=>[:insert_row, :delete_row, :copy_row]で起こること

上記の解読を:member=>[:insert_row, :delete_row, :copy_row]で追記されたルート規則に当てはめてみると...

#           名前付きルート名 メソッド  URLパス書式                       処理されるコントローラー、アクション
#-----------------------------------------------------------------------------------------------------------
          insert_row_slip        /slips/:id/insert_row            {:controller=>"slips", :action=>"insert_row"}
            copy_row_slip        /slips/:id/copy_row              {:controller=>"slips", :action=>"copy_row"}
          delete_row_slip        /slips/:id/delete_row            {:controller=>"slips", :action=>"delete_row"}
    • メソッド名は指定が無いので、GET、POST、PUT、DELETE何でも対象
    • URLがhttp: //localhost:3000/slips/:id*2/insert_row(またはdelete_row、copy_row)という書式
  • 以上二つの条件を満たすリクエストは、slipsコントローラーのinsert_row(またはdelete_row、copy_row)アクションを実行する。
  • さらにルート規則は、上にある規則の方が優先順位が高いので、その結果、PUTメソッドであってもinsert_rowアクションが実行されることになる。
  • オレンジ色の追加されたルート規則が無い頃は、以下のルート規則が適用され、常にupdateアクションが呼び出される結果になっていたのだ...。
#           名前付きルート名 メソッド  URLパス書式                       処理されるコントローラー、アクション
#-----------------------------------------------------------------------------------------------------------
                          PUT    /slips/:id                       {:controller=>"slips", :action=>"update"}


ルートの理解が、少しだけ進んだ。

疑問

悩んでいるうちに、疑問がさらに深まってきた...。

  • そもそもlink_to_remoteはPOSTメソッドでリクエストしているはずだ。
  • それなのにform_forが補う:_method=>'put'によって、本来POSTであって欲しいリクエストがPUTと解釈されてしまうところに問題がある。
  • :_method=>'put'が送信されるのは、submitボタンを押した時だけにして欲しいものだ。
  • それとも:_method=>'put'が渡されても、POSTメソッドが優先される方法ってあるのだろうか?
  • 前回も今回もどちらも根本的な対策ではない。もしブラウザが正式にPUTメソッドをサポートしてたら、このような問題は起こらないのだから...。

ベストな解決策はどのようにすべきだろう?

以下の日記に続く...

*1:localhost:3000の部分は環境により変化する

*2::idは任意のid値