RailsのControllerで、以下のような複数itemで構成されるinputのvalueを配列で受け取りたい場合。
<div>
<h2>Item1</h2>
<input type="text" name="item[name_0]" value="yamada" />
</div>
<div>
<h2>Item2</h2>
<input type="text" name="item[name_1]" value="suzuki" />
</div>
このまま送信するとController#paramsの内容はこのようになる。
{
"item" => {
"name_0" => "yamada",
"name_1" => "suzuki"
}
}
もちろんこれでは配列で取得できない。そこで以下のようにする。
<div>
<h2>Item1</h2>
<input type="text" name="item[][name]" value="yamada" />
</div>
<div>
<h2>Item2</h2>
<input type="text" name="item[][name]" value="suzuki" />
</div>
HTMLでitem[][name]とすると、params[:item]は配列になる
{
"item" => [
{"name" => "yamada"},
{"name" => "suzuki"}
]
}
お、じゃあ、これでいいじゃん。と思ったら落とし穴が待っていた。checkboxである。
input type="checkbox"は、チェックした場合にしか送信されない。それは都合が悪い場合も多いので、input type="hidden"にチェックボックスオフ時の値をvalueに入れておくのが定石になっている。
<input type="hidden" name="item[delete_flag]" value="0" />
<input type="checkbox" name="item[delete_flag]" value="1" />
こうすると、チェックしない時はhiddenのvalueが送信され、チェックした時はcheckboxのvalueが送信される。Railsのform helperのcheck_boxメソッドも、hiddenとcheckboxを一緒に出力するようになっている。
<%= form_for User.new do |f| %>
<% # input type="hidden"と"checkbox"を出力する %>
<%= f.check_box :delete_flag %>
<% end %>
で、話を戻して配列化の話。checkboxもこのようにすると良いように思える。
<div>
<h2>Item1</h2>
<input type="text" name="item[][name]" value="yamada" />
<input type="hidden" name="item[][delete_flag]" value="0" />
<input type="checkbox" name="item[][delete_flag]" value="1" />
</div>
<div>
<h2>Item2</h2>
<input type="text" name="item[][name]" value="suzuki" />
<input type="hidden" name="item[][delete_flag]" value="0" />
<input type="checkbox" name="item[][delete_flag]" value="1" />
</div>
これでItem1のcheckbox(delete_flag)はチェックなし、Item2はチェックありで送信した時、Controller#paramsの内容はこうなるだろうと期待する。
{
"item"=>[
{"name"=>"yamada", "delete_flag"=>"0"},
{"name"=>"suzuki", "delete_flag"=>"1"}
]
}
しかし、実際はこうなる・・・
{
"item"=>[
{"name"=>"yamada", "delete_flag"=>"0"},
{"name"=>"suzuki", "delete_flag"=>"0"},
{"delete_flag"=>"1"}
]
}
逆にItem1はチェックあり、Item2はチェックなしにするとこうなる。
{
"item"=>[
{"name"=>"yamada", "delete_flag"=>"0"},
{"delete_flag"=>"1", "name"=>"suzuki"},
{"delete_flag"=>"0"}
]
}
だめだめである・・・
いくつか方法はあると思うが、私はこのようにすることにした。
<input type="text" name="item[0][name]" value="yamada" />
<input type="hidden" name="item[0][delete_flag]" value="0" />
<input type="checkbox" name="item[0][delete_flag]" value="1" />
<input type="text" name="item[1][name]" value="suzuki" />
<input type="hidden" name="item[1][delete_flag]" value="0" />
<input type="checkbox" name="item[1][delete_flag]" value="1" />
item[]ではなくitem[0]、item[1]のようにindex値を入れるようにした。そしてItem1はチェックなし、Item2はチェックありで送信すると内容はこうなる。
{
"item"=>
{
"0"=>{"name"=>"yamada", "delete_flag"=>"0"},
"1"=>{"name"=>"suzuki", "delete_flag"=>"1"}
}
}
Item1チェックあり、Item2はチェックなしの場合はこうなる。
{
"item"=>
{
"0"=>{"name"=>"yamada", "delete_flag"=>"1"},
"1"=>{"name"=>"suzuki", "delete_flag"=>"0"}
}
}
itemは配列ではなくHashになるが、以下のように簡単に配列化できる。
items = params[:item].keys.sort.map { |index| params[:item][index] }
itemsの内容は以下のようになる。
[
{"name"=>"yamada", "delete_flag"=>"0"},
{"name"=>"suzuki", "delete_flag"=>"1"}
]
form helperを使って出力する
これまでは理解しやすいように直接HTMLタグを直接書いてきたが、実際はform helperを使うことになるだろう。その場合は、以下のようにname属性を明示的に指定すると良い。
<%= form_for @group, url: url_for(action: :debug) do |f| %>
<% @group.items.each_with_index do |item, index| %>
<%= f.fields_for item do |f| %>
<%= f.text_field :name, name: "item[#{index}][name]" %>
<%= f.check_box :delete_flag, name: "item[#{index}][delete_flag]" %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
@groupは、以下のようなItemGroupモデルのオブジェクトとする。
class ItemGroup
# Itemとのリレーション
has_many :items
end
class Item
# itemsテーブルは、name, delete_flagをカラムに持つ
end
ActiveAdminのsemantic_form_forの場合
以下のように書くことができる。
<%= semantic_form_for [:admin, @group],
builder: ActiveAdmin::FormBuilder do |f| %>
<% @group.items.each_with_index do |item, index| %>
<%= f.fields_for :items, items do |f| %>
<%= f.input :name,
input_html: { name: "item[#{index}][name]" } %>
<%= f.input :delete_flag,
input_html: { name: "item[#{index}][delete_flag]" } %>
<% end %>
<% end %>
<% end %>