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"}
- 以上二つの条件を満たすリクエストは、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メソッドをサポートしてたら、このような問題は起こらないのだから...。
ベストな解決策はどのようにすべきだろう?