参考サイト
htmlの生成部は、とても参考になりました。
Railsで表形式の一括更新 - このブログは証明できない。
作ってみた
view
<h1>Listing exchanges</h1> <% form_tag(:action => "update") do %> <table> <tr> <th>Name</th> <th>Rate</th> </tr> <%- @exchanges.each do |exchange| -%> <%- fields_for exchange, :index => exchange.id do |f| -%> <%= f.error_messages %> <tr> <td><%= f.text_field :name %></td> <td><%= f.text_field :rate %></td> </tr> <%- end -%> <%- end -%> </table> <p><%= submit_tag "一括更新" %></p> <% end -%>
post時のhashは下記のようになります。
Parameters: {"commit"=>"一括更新", "authenticity_token"=>"E8iHdTe9b1fj50XWQBt8L4JmSfC3Qg+z8hHAMSAz5tM=", "exchange"=>{"1"=>{"name"=>"USD", "rate"=>"82.6655123456"}, "2"=>{"name"=>"EUR", "rate"=>"117.966212345"}}}
model
class Exchange < ActiveRecord::Base validates_length_of :name, :maximum => 3 end
エラー時のrollbackを確認するために、validates_length_ofを入れときます。
controller(最初に書いたver)
def update @exchanges = Exchange.find(params[:exchange].keys) err_flg = false begin Exchange.transaction do @exchanges.map do |exchange| params[:exchange].each do |k,v| if exchange.id.to_s == k err_flg = true if exchange.update_attributes(v) break end end end raise 'upd error' if err_flg end redirect_to(exchanges_url, :notice => 'ok') rescue => ex render :action => 'index' end end
う〜ん、これはダメそうな匂いがします。
調べてみると
ActiveRecord::Base (rails3ではActiveRecord::Relation)に
update(id, attributes)というメソッドがあるではないですか。
exampleも、まさに複数行更新用です。
# Updates multiple records people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } Person.update(people.keys, people.values)
でも、これエラーハンドリングしてませんね。
おしい。。
なんとか
これを利用できないかとソースを見てみると
module ActiveRecord #:nodoc: class Base class << self # Class methods def update(id, attributes) if id.is_a?(Array) idx = -1 id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } else object = find(id) object.update_attributes(attributes) object end end
更新後のobjectを返却しています。
ふむふむ使えそうです。
結局
controller(こうなりました)
#raiseは手抜き
def update begin Exchange.transaction do @exchanges = Exchange.update(params[:exchange].keys, params[:exchange].values) raise 'upd error' if @exchanges.find{ |exc| exc.errors.count != 0 } end redirect_to(exchanges_url, :notice => 'ok') rescue render :action => 'index' end end
感想
複数エラーの場合は、scaffoldのエラー出力が複数出力されてしまい
見た目が汚いです。
この辺りをうまくやるには、モデルを入れ子にした構造で作ってやればなんとか出来そうです。
毎度思いますが、ちょっと変わったことやろうとすると面倒。