2.0の:_methodには:_methodで対抗!
以前から悩んでいた、link_to_remoteでリクエストがPUTメソッドとして扱われてしまい、常にupdateアクション呼び出しになってしまう現象への対策の続き...
PUT問題の始まり
- 以下のような環境で、link_to_remoteのリンクをクリックすると、挿入・削除・コピーとも常にupdateアクションが実行されてしまい、困っていた...。
# ルート設定: config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :slips map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end
# ビュー: app/views/slips/edit.html.erb <div class="slip" id="slip"> <h1><%= _('Editing slip') %></h1> <% form_for @slip do |f| %> ...(中略)... <%= link_to_remote "1行挿入", :submit=>'slip', :url=>{:action=>'insert_row', :index=>form.index} %> <%= link_to_remote "1行削除", :submit=>'slip', :url=>{:action=>'delete_row', :index=>form.index} %> <%= link_to_remote "1行コピー", :submit=>'slip', :url=>{:action=>'copy_row', :index=>form.index} %> ...(中略)... <% end %> </div>
- 原因はform_forが見えないinputタグを生成して、:_method=>"put"を送信しているためと思われるが、それではどうすればよいか?
# ビュー: app/views/slips/edit.html.erb <div class="slip" id="slip"> <h1><%= _('Editing slip') %></h1> <% form_for @slip do |f| %><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /></div> ...(中略)... <%= link_to_remote "1行挿入", :submit=>'slip', :url=>{:action=>'insert_row', :index=>form.index} %> <%= link_to_remote "1行削除", :submit=>'slip', :url=>{:action=>'delete_row', :index=>form.index} %> <%= link_to_remote "1行コピー", :submit=>'slip', :url=>{:action=>'copy_row', :index=>form.index} %> ...(中略)... <% end %> </div>
これまで以下の対策を考えていたが、それぞれ問題があった...。
- :submitの送信範囲を限定して、<input name="_method" type="hidden" value="put" />を送信しない範囲を設定する。
- →form_forを含んだ範囲の送信が必須の状況ではどうしようもない...。
- ルート設定に:member=>[:insert_row, :delete_row, :copy_row]オプションを追加する。
- →RESTなHTTPメソッドの使い方を無視している...。(:delete_rowアクションをPUTで実行することになってしまう。)
悩んでいるうちに、さらに疑問は深まり...
- 「:_method=>'put'が送信されるのは、submitボタンを押した時だけにして欲しいものだ。」
- →link_to_remoteの:submitの範囲にform_forが含まれている場合、<input name="_method" type="hidden" value="put" />の送信を止めることは出来ない。
- →よって、link_to_remoteの時:_method=>"put"を送信したくないのであれば、:submitの範囲指定を、:_method=>"put"を含まない範囲に限定するしかない...。
- 「それとも:_method=>'put'が渡されても、POSTメソッドが優先される方法ってあるのだろうか?」
- →未解決...。
そして、最近閃いた。(詳細は以下)
:_methodには:_methodで対抗する
- :method=>:postオプションを指定してみたが、それでもRailsはPUTと解釈してしまう。:_methodパラメーターが優先されているようだ...。
<%= link_to_remote "1行挿入", :submit=>'slip', :url=>{:action=>'insert_row', :index=>form.index}, :method=>:post %> <%= link_to_remote "1行削除", :submit=>'slip', :url=>{:action=>'delete_row', :index=>form.index}, :method=>:post %> <%= link_to_remote "1行コピー", :submit=>'slip', :url=>{:action=>'copy_row', :index=>form.index}, :method=>:post %>
- そうか!それなら:_methodパラメーターを、urlパラメーターとして再設定してみる。
<%= link_to_remote "1行挿入", :submit=>'slip', :url=>{:action=>'insert_row', :index=>form.index, :_method=>:post}, :method=>:post %> <%= link_to_remote "1行削除", :submit=>'slip', :url=>{:action=>'delete_row', :index=>form.index, :_method=>:post}, :method=>:post %> <%= link_to_remote "1行コピー", :submit=>'slip', :url=>{:action=>'copy_row', :index=>form.index, :_method=>:post}, :method=>:post %>
- うまくいった!これでeditページでも挿入・削除・コピーがちゃんと動くようになった!(今までなぜこの方法が思い付かなかったのか...。)
- ただし、Railsでは、urlパラメーターはformパラメーターよりも優先されるという経験則に基づいている。(正しい認識だろうか?)
REST風に...
せっかく、map.resourcesのオプションを覚えたので、さっそく利用してみる。
- :collectionオプション設定を追記する。
# ルート設定: config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :slips, :collection=>{:insert_row=>:post, :copy_row=>:post, :delete_row=>:delete} map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end
- rake routesで確認してみる。オレンジ色の部分が:collectionオプションによって追加された。
insert_row_slips POST /slips/insert_row {:controller=>"slips", :action=>"insert_row"}
formatted_insert_row_slips POST /slips/insert_row.:format {:controller=>"slips", :action=>"insert_row"}
copy_row_slips POST /slips/copy_row {:controller=>"slips", :action=>"copy_row"}
formatted_copy_row_slips POST /slips/copy_row.:format {:controller=>"slips", :action=>"copy_row"}
delete_row_slips DELETE /slips/delete_row {:controller=>"slips", :action=>"delete_row"}
formatted_delete_row_slips DELETE /slips/delete_row.:format {:controller=>"slips", :action=>"delete_row"}
slips GET /slips {:controller=>"slips", :action=>"index"}
formatted_slips GET /slips.:format {:controller=>"slips", :action=>"index"}
POST /slips {:controller=>"slips", :action=>"create"}
POST /slips.:format {:controller=>"slips", :action=>"create"}
new_slip GET /slips/new {:controller=>"slips", :action=>"new"}
formatted_new_slip GET /slips/new.:format {:controller=>"slips", :action=>"new"}
edit_slip GET /slips/:id/edit {:controller=>"slips", :action=>"edit"}
formatted_edit_slip GET /slips/:id/edit.:format {:controller=>"slips", :action=>"edit"}
slip GET /slips/:id {:controller=>"slips", :action=>"show"}
formatted_slip GET /slips/:id.:format {:controller=>"slips", :action=>"show"}
PUT /slips/:id {:controller=>"slips", :action=>"update"}
PUT /slips/:id.:format {:controller=>"slips", :action=>"update"}
DELETE /slips/:id {:controller=>"slips", :action=>"destroy"}
DELETE /slips/:id.:format {:controller=>"slips", :action=>"destroy"}
/:controller/:action/:id
/:controller/:action/:id.:format
- RESTな名前付きルートも利用してみる。
# ビュー: app/views/slips/edit.html.erb <div class="slip" id="slip"> <h1><%= _('Editing slip') %></h1> <% form_for @slip do |f| %> ...(中略)... <%= link_to_remote "1行挿入", :submit=>'slip', :url=>insert_row_slips_path(:index=>form.index, :_method=>:post), :html=>{:title=>"1行挿入"}, :method=>:post %> <%= link_to_remote "1行削除", :submit=>'slip', :url=>delete_row_slips_path(:index=>form.index, :_method=>:delete), :html=>{:title=>"1行削除"}, :method=>:delete %> <%= link_to_remote "1行コピー", :submit=>'slip', :url=>copy_row_slips_path(:index=>form.index, :_method=>:post), :html=>{:title=>"1行コピー"}, :method=>:post %> ...(中略)... <% end %> </div>
これまでのPUT対策で一番気に入った方法なのだが、果たしてこれが良い方法なのかどうか...。悩み続けてもしょうがないので、さらに良い方法が見つかるまではこの作戦でやってみる!
-
-
- Rails 2.1では「insert_row_slips_path(:index=>form.index, :_method=>:post)」オレンジ部分のような回りくどい方法は不要になった。
- 素直に「:method=>:post」オプションのみでPOSTメソッドとして送信してくれる!
-