Posted at

formのinput valueを配列で受け取りたい場合のname属性

More than 1 year has passed since last update.

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 %>