rails-bootstrap-formsでmultiple: trueを使った時にinputのidがlabelのforと一致しない問題

# 発端

[rails-bootstrap-forms](https://github.com/bootstrap-ruby/rails-bootstrap-forms)を使っていて、何故かラベルをクリックしてもチェックボックスの操作ができない！となったので、原因を調査してみました。

# 原因

ラベルのfor属性と、チェックボックスのidが一致していなかった。
問題のerbがこちら。

```erb
<%= bootstrap_form_tag method: :get, url: search_path, layout: :horizontal do |f| %>
  <%= f.check_box :target_types, { multiple: true, include_hidden: false, checked: params[:target_types].blank? || params[:target_types].include?("Test::Alpha") }, "Test::Alpha" %>
  <%= f.check_box :target_types, { multiple: true, include_hidden: false, checked: params[:target_types].blank? || params[:target_types].include?("Test::Beta") }, "Test::Beta" %>
  <%= f.submit "Search", class: 'btn btn-primary' %>
<% end %>
```

生成されるhtml（問題の部分のみ抽出）

```html
<div class="checkbox">
  <label for="_target_types_Test::Alpha">
    <input checked="checked" id="_target_types_testalpha" name="[target_types][]" type="checkbox" value="Test::Alpha"> Target types
  </label>
</div>
```

labelのfor属性が`"_target_types_Test::Alpha"`になっているが、inputのidが`"_target_types_testalpha"`となっている。

より詳しい原因を究明するために、rails-bootstrap-formsの実装を追っていく。

https://github.com/atipugin/rails-bootstrap-forms/blob/master/lib/bootstrap_form/form_builder.rb#L101-L118

```rb
def check_box_with_bootstrap(name, options = {}, checked_value = "1", unchecked_value = "0", &block)
  options = options.symbolize_keys!

  html = check_box_without_bootstrap(name, options.except(:label, :help, :inline), checked_value, unchecked_value)
  label_content = block_given? ? capture(&block) : options[:label]
  html.concat(" ").concat(label_content || (object && object.class.human_attribute_name(name)) || name.to_s.humanize)

  label_name = name
  label_name = "#{name}_#{checked_value}" if options[:multiple]

  if options[:inline]
    label(label_name, html, class: "checkbox-inline")
  else
    content_tag(:div, class: "checkbox") do
      label(label_name, html)
    end
  end
end
```

ここらへんにbinding.pryを仕掛けて処理を追っていく。

まずラベルの生成は`label(label_name, html)`で、[ActionView::Helpers::FormBuilder#label](https://github.com/rails/rails/blob/9f44aa08636dfbba9261f4350ec14684425c4b7b/actionview/lib/action_view/helpers/form_helper.rb#L1649-L1651)に飛ばしている。
multiple: trueにした場合、
`label_name = "#{name}_#{checked_value}" if options[:multiple]`で、ここでは`"_target_types_Test::Alpha"`が入っている。

```rb
def label(method, text = nil, options = {}, &block)
  @template.label(@object_name, method, text, objectify_options(options), &block)
end
```

`method = "_target_types_Test::Alpha"`。
ここで[こっち](https://github.com/rails/rails/blob/9f44aa08636dfbba9261f4350ec14684425c4b7b/actionview/lib/action_view/helpers/form_helper.rb#L765-L767)が呼び出される

```rb
def label(object_name, method, content_or_options = nil, options = nil, &block)
  Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
```

その後なんやかんやあって`method = "_target_types_Test::Alpha"`がlabelのfor属性に設定される。（省略）

一方、inputのidについて追っていくと、inputタグの生成は`check_box_without_bootstrap`でRails側に投げており、[ここで](https://github.com/rails/rails/blob/52ce6ece8c8f74064bb64e0a0b1ddd83092718e1/actionview/lib/action_view/helpers/tags/base.rb#L76)、valueの値（"Test::Alpha"）を[sanitized](https://github.com/rails/rails/blob/52ce6ece8c8f74064bb64e0a0b1ddd83092718e1/actionview/lib/action_view/helpers/tags/base.rb#L116-L118)したものがidとして設定される。

```rb
options["id"] += "_#{sanitized_value(tag_value)}"
```

```rb
def sanitized_value(value)
  value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
end
```

これで`"Test::Alpha" -> "testalpha"`に変換され、idが`"_target_types_testalpha"`になる。

Railsの仕様により、inputのidはsanitizedされたものが使われるが、rails-bootstrap-forms内でlabelのfor属性に当てる文字列をsanitizedしていなかったのが原因。

# 解決策

とりあえずの解決策としては、checkboxにidを指定する

```diff
<%= bootstrap_form_tag method: :get, url: search_path, layout: :horizontal do |f| %>
-  <%= f.check_box :target_types, { multiple: true, include_hidden: false, checked: params[:target_types].blank? || params[:target_types].include?("Test::Alpha") }, "Test::Alpha" %>
+  <%= f.check_box :target_types, { multiple: true, include_hidden: false, checked: params[:target_types].blank? || params[:target_types].include?("Test::Alpha"), id: "_target_types_Test::Alpha" }, "Test::Alpha" %>
-  <%= f.check_box :target_types, { multiple: true, include_hidden: false, checked: params[:target_types].blank? || params[:target_types].include?("Test::Beta") }, "Test::Beta" %>
+  <%= f.check_box :target_types, { multiple: true, include_hidden: false, checked: params[:target_types].blank? || params[:target_types].include?("Test::Beta"), id: "_target_types_Test::Beta" }, "Test::Beta" %>
  <%= f.submit "Search", class: 'btn btn-primary' %>
<% end %>
```

が、どう見てもrails-bootstrap-formsのバグなので、Pull Requestを送ろうかなあと思ったら[既にあった](https://github.com/bootstrap-ruby/rails-bootstrap-forms/pull/258)。
~~放置されてるっぽい…う〜ん。~~
いつのまにかMergeされてました。めでたしめでたし。