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で悩みそうだ...。