2.0のREST、ラビリンス状態...。
- 前回、伝票の明細行を挿入・削除・コピーするためのルート設定を:collectionオプションで以下のように設定した。
# ルート設定: config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :slips, :collection=>{:insert_row=>:post, :copy_row=>:post, :delete_row=>:delete} end
- しかし、複数の伝票に対する操作ではないのだから、:collectionではないと思い始める。(それにしても、本来:collectionでないルート規則であっても、:collectionでなんとか設定できてしまうことが再発見であった。)
- :collectionでないとすると:memberか:newになる訳だが、:idがある場合と、ない場合どちらもあり得る。
- それなら、:memberと:new両方とも設定が必要か?
- そもそも、明細行に対する操作なのだから、ネストしたルート設定で以下のようにする必要があったのではないか...。
# ルート設定: config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :slips do |slip|
slip.resources :journals, :collection=>{:insert_row=>:post, :copy_row=>:post, :delete_row=>:delete}
end
end
# rake routesの結果(:collectionオプションによる設定のみ表示)
...(中略)...
insert_row_slip_journals POST /slips/:slip_id/journals/insert_row {:controller=>"journals", :action=>"insert_row"}
formatted_insert_row_slip_journals POST /slips/:slip_id/journals/insert_row.:format {:controller=>"journals", :action=>"insert_row"}
copy_row_slip_journals POST /slips/:slip_id/journals/copy_row {:controller=>"journals", :action=>"copy_row"}
formatted_copy_row_slip_journals POST /slips/:slip_id/journals/copy_row.:format {:controller=>"journals", :action=>"copy_row"}
delete_row_slip_journals DELETE /slips/:slip_id/journals/delete_row {:controller=>"journals", :action=>"delete_row"}
formatted_delete_row_slip_journals DELETE /slips/:slip_id/journals/delete_row.:format {:controller=>"journals", :action=>"delete_row"}
...(中略)...
- 伝票が新規作成中の場合も考える必要がある...。
- ブログシステムを例として考えれば、記事(Entry)とコメント(Comment)の関係になり、コメントは記事が登録済みであることが前提だ。だから、コメントを追加する時には記事は必ずidを持った状態になる。よって、ブログシステムの例では、記事の新規作成中にコメントを追加するケースは考える必要がない。
- ところが、伝票入力システムでは伝票と明細行は同時に登録される。そうすると、伝票を新規作成中のidが無い状態で、明細行を追加する状況も考えておく必要がある...。
# ルート設定: config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :journals,
:path_prefix=>'/slips/new', :name_prefix=>'slip_new_',
:collection=>{:insert_row=>:post, :copy_row=>:post, :delete_row=>:delete}
map.resources :slips do |slip|
slip.resources :journals, :collection=>{:insert_row=>:post, :copy_row=>:post, :delete_row=>:delete}
end
end
# rake routesの結果(map.resources :journalsの:collectionオプションによる設定のみ表示)
...(中略)...
insert_row_slip_new_journals POST /slips/new/journals/insert_row {:controller=>"journals", :action=>"insert_row"}
formatted_insert_row_slip_new_journals POST /slips/new/journals/insert_row.:format {:controller=>"journals", :action=>"insert_row"}
copy_row_slip_new_journals POST /slips/new/journals/copy_row {:controller=>"journals", :action=>"copy_row"}
formatted_copy_row_slip_new_journals POST /slips/new/journals/copy_row.:format {:controller=>"journals", :action=>"copy_row"}
delete_row_slip_new_journals DELETE /slips/new/journals/delete_row {:controller=>"journals", :action=>"delete_row"}
formatted_delete_row_slip_new_journals DELETE /slips/new/journals/delete_row.:format {:controller=>"journals", :action=>"delete_row"}
...(中略)...
- 以上の設定をすると、今までslips_controllerで処理していた挿入・削除・コピーが、journals_controllerに変更になってしまう。その部分のコードの引っ越しが必要だ。
- insert_row、copy_row、delete_row、考えてみるとアクション名が変だ。動詞+目的語というslips_controller用のネーミングになっている。「_row」は必要ない。
- ネストしたルート設定map.resources :slips {|slip| slip.resources :journals, ...}は、果たして必要だろうか?
- 明細行の挿入・削除・コピーが、常にブラウザが表示するフォーム上だけのリソース(DB未登録の一時的なリソース)に対しての操作であることを考えれば、:path_prefix=>'/slips/new'だけで十分ではないか?
- 実際、:slip_idパラメーターはコードの中で利用していない。
- そして、そんな風に悩みながら辿り着いたのが以下のコード。
# ルート設定: config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :journals,
:path_prefix=>'/slips/new', :name_prefix=>'slip_new_',
:collection=>{:insert=>:post, :copy=>:post, :delete=>:delete}
map.resources :slips
end
# rake routesの結果(map.resources :journalsの:collectionオプションによる設定のみ表示)
...(中略)...
insert_slip_new_journals POST /slips/new/journals/insert {:controller=>"journals", :action=>"insert"}
formatted_insert_slip_new_journals POST /slips/new/journals/insert.:format {:controller=>"journals", :action=>"insert"}
copy_slip_new_journals POST /slips/new/journals/copy {:controller=>"journals", :action=>"copy"}
formatted_copy_slip_new_journals POST /slips/new/journals/copy.:format {:controller=>"journals", :action=>"copy"}
delete_slip_new_journals DELETE /slips/new/journals/delete {:controller=>"journals", :action=>"delete"}
formatted_delete_slip_new_journals DELETE /slips/new/journals/delete.:format {:controller=>"journals", :action=>"delete"}
...(中略)...
# ビュー: app/views/journals/_form.html.erb <% fields_for form, :index=>form.index do |j| %> ...(中略)... <%= link_to_remote "1行挿入", :submit=>'slip', :url=>insert_slip_new_journals_path(:index=>form.index, :_method=>:post), :html=>{:title=>"1行挿入"}, :method=>:post %> <%= link_to_remote "1行削除", :submit=>'slip', :url=>delete_slip_new_journals_path(:index=>form.index, :_method=>:delete), :html=>{:title=>"1行削除"}, :method=>:delete %> <%= link_to_remote "1行コピー", :submit=>'slip', :url=>copy_slip_new_journals_path(:index=>form.index, :_method=>:post), :html=>{:title=>"1行コピー"}, :method=>:post %> ...(中略)... <% end %> </div>
# コントローラー: app/controllers/journals_controller.rb class JournalsController < ApplicationController before_filter :for_editing_rows, :only=>[:insert, :copy, :delete] ...(中略)... private # 挿入、削除、コピーの前処理 def for_editing_rows @slip = Slip.new(params[:slip]) @slip.make_journals(params[:journal]) end def render_insert(position) render :update do |page| page.insert_html position, params[:index], render(:partial=>'journals/form', :object=>@effect_item) highlight_row(page, @effect_item.index, :duration=>2) numbering_row(page, @effect_item.position - 1) page.replace 'journals_footer', :partial=>'journals/footer' end end public def insert @effect_item = @slip.insert_journal(params[:index]) render_insert(:before) end def copy @effect_item = @slip.copy_journal(params[:index]) render_insert(:after) end def delete @effect_item = @slip.delete_journal(params[:index]) render :update do |page| highlight_row(page, @effect_item.index, :duration=>2, :startcolor=>"'#666666'") page.delay(1) do page.remove params[:index] numbering_row(page, @effect_item.position - 1) page.replace 'journals_footer', :partial=>'journals/footer' end end end end
- ここまでやっておいて、単純に:newオプションだけ変更した以下でも良いような気がしてきた...。
- そもそも、map.resourcesのデフォルトに、:path_prefix=>'/slips/new'という考え方は無いのだから...。
# ルート設定: config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :slips, :new=>{:insert_row=>:post, :copy_row=>:post, :delete_row=>:delete} end
- SlipsControllerとJournalsControllerのどちらに仕事をさせるのか、そこが問題なのかもしれない。この状況では、一般的にどのような設計になるべきだろうか?
- ちなみに、この伝票入力システムのサンプルコードを会計システムに発展させるとなると、伝票の明細行(Journal)は集合して仕訳帳となり、JournalsControllerはその仕訳帳を管理する役割も出てくる。
暫くRESTで悩みそうだ...。