: O. Yuanying

もう一つのmultiparameter assignment を応用したフォームの入力

multiparameter assignment を応用したフォームの入力」より。

問題

以下のようなフォームをRailsでうまく扱う。

あなたのご興味(複数選択可)
コンピュータ
旅行

Viewには次のように記述。

<input type="checkbox" name="user[interests(1s)]" value="コンピュータ"/>コンピュータ
<input type="checkbox" name="user[interests(2s)]" value="旅行" />旅行
<input type="checkbox" name="user[interests(3s)]" value="車" />車

こんなやり方も?

元のActiveRecord::Base#execute_callstack_for_multiparameter_attributes関数を読んでみる。

def execute_callstack_for_multiparameter_attributes(callstack)
  errors = []
  callstack.each do |name, values|
    klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
    if values.empty?
      send(name + "=", nil)
    else
      begin
        send(name + "=", Time == klass ? (@@default_timezone == :utc ? klass.utc(*values) : klass.local(*values)) : klass.new(*values))
      rescue => ex
        errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
      end
    end
  end
  unless errors.empty?
    raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
  end
end

ここで注目したいのは、klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attributeと、klass.new(*values)。

ActiveRecord::Base.reflect_on_aggregation()はどうやら関連や合成(Composed)クラスを取得してくるようだ。そこでnameに関連づけられたクラスをklass変数に保持して、klass.new(*values)で生成すると。

RailsでUglyになんとかTimeZoneをサポートする」でもうまくいってたので、こんな感じでもうまくいきそうだ。

class User < ActiveRecord::Base
  composed_of :interest_list, :class_name => 'Array'
  def interest_list=(list)
    interests = list.join('|')
  end
end

元のActiveRecord::Base#execute_callstack_for_multiparameter_attributes関数を上書きしたりしなくてすむのでこれでも良いかなと思ったりしないでも無い。

勘違い

すでに元記事の方でフォローされてるが、Array.new(item,...])というコンストラクタが無いため、上記の方法はうまくいかない。このサンプルコードを書くまえにちらっとArrayのリファレンスを見たんだけど、Array[item,...]をArray.new(item,...)と見間違えたらしい^^;

という言い訳などを…。

人のあら探ししてる暇あったら自分でちゃんと検証してみましょうね!