Railsで明細行の一括登録・更新

参考ページ:leave a note [message] behind on Rails: 明細行のtext_fieldなどをフォームで配列化する on Rails

目的:部署別の売上を一括登録する
Railのバージョン:2.0.2
Scaffoldで生成されるメンテナンス画面でもいいけど、一つずつ選択して登録・更新するのは面倒なので、一括して入力できる画面を作る。こんな感じの画面。

上の図は次のテーブルの内容を一括して登録・更新する感じ。(codeはユニークとしておく)

CREATE TABLE UNIT_SALES (
  id serial NOT NULL,
  code haracter varying(4) NOT NULL,
  name haracter varying(4) NOT NULL,
  amount integer,
)

初期表示のためにunit_salesテーブルからデータを取得し@units変数へ入れておく処理をコントローラに追加。

def index
  @units = UnitSale.find(:all)
end

次に、index.rhtmlで参考ページにあるようなやり方でデータを表示させる。

<% form_for :sample, :url => {:action => :update } do |f| %>
<table border="1" cellspacing="0">
  <tr>
    <td colspan="3"><%= f.submit "更新" %>
  </tr>
  <tr bgcolor="#fecde5">
    <th>部署コード</th>
    <th>部署名</th>
    <th>売上</th>
  </tr>
  <% @units.each_with_index{|@r, idx| %>
  <tr>
  	<td><%=h @r.code %></td>
  	<td><%=h @r.name %></td>
    <td>
        <%= hidden_field 'r', :code, :index => idx %>
    	<%= text_field 'r', :amount, :size => 13, :maxlength => 11, :index => idx, :style=>"text-align:right" %></td>
  </tr>
  <%}%>
</table>
<% end %>

以下の部分は、コントローラで取得した@unitsの中身を連番(idx)と一緒に取り出して(@r)いる。@unitsの一レコードのデータはインスタンス変数@rとしないといけないらしい(rの部分はどんな名前でもよい)。

<% @units.each_with_index{|@r, idx| %>

テキストボックス、HIDDENフィールドの部分は次のようにしておく。hidden_field,text_fieldの第1引数に上記の変数@rを指定(@は書かない)。:indexには上記idxを指定する。(参考にしたページには text_field 'r'・・というようにを指定していたけど、なくてもできた)

<%= hidden_field 'r', :code, :index => idx %>
<%= text_field 'r', :amount, :size => 13, :maxlength => 11, :index => idx, :style=>"text-align:right" %>

こうすることで生成されるHTMLは以下のようになる。

<input id="r_0_code" name="r[0][code]" type="hidden" value="0001" />
<input id="r_0_amount" maxlength="11" name="r[0][amount]" size="13" style="text-align:right" type="text" value="12354" />

name="r[0][code]"のようにidxの部分がr[]の中に設定される。

この値をコントローラで受け取って、登録・更新を行う。

def update
  update_data = params[:r]
  update_data.each {|key, value|
    unit = UnitSale.find(:first, :conditions => ['code = ?', value.code])
    unless unit
      # データがなかった場合、新規登録するためオブジェクトを生成する
      unit = UnitSale.new(value)
    end
    unit.amount = value.amount # 売上を設定
    unit.save
  }
end

ここで、params[:r]で受け取ったデータは以下のようなHashの配列になっている。

[
  {"0" => {"code" => "0001", "amount" => "12345"}},
  {"1" => {"code" => "0002", "amount" => "3232"}},
  ・・・省略
]

そこで、これらの配列の中身をひとつずつ処理していくとき、キーの部分と値の部分に分けて処理していく。

update_data.each {|key, value|

ここで、keyには「0,1,2・・」という値が入り、valueには{"code" => "0001", "amount" => "12345"}という値が入ることになる。あとは、取得したデータからレコードを検索し、存在したら更新、存在しなかったら新規登録という処理を追加する。