`collection_radio_buttons` / `collection_check_boxes` でnil値のlabel `for`とinput `id`が一致しないバグを修正

rails/rails

collection_radio_buttonscollection_check_boxes のコレクションに nil が含まれる場合、ラベルの for 属性がインプットの id と一致しないバグが修正されました。これにより、nil 値を持つ選択肢のラベルをクリックしても対応するラジオボタンやチェックボックスが選択されなかった問題が解消されます。

背景

nil を含むコレクションを collection_radio_buttons に渡した際、生成されるHTMLに不整合が生じていました。具体的には、for 属性に末尾アンダースコアが付いた user_active_ が出力される一方、id 属性には user_active が出力されるため、両者が一致しない状態になっていました。

collection_radio_buttons(:user, :active, [["Yes", true], ["Undefined", nil]], :last, :first)

上記のコードが生成するHTMLは次のようになっていました:

<!-- ✅ true — for と id が一致 -->
<input id="user_active_true" type="radio" value="true" />
<label for="user_active_true">Yes</label>

<!-- ❌ nil — 末尾アンダースコアの不一致 -->
<input id="user_active" type="radio" value="" />
<label for="user_active_">Undefined</label>

この挙動は、sanitize_attribute_name36cb715 で導入された2012年から継続していたものです。

技術的な変更

根本原因は、idfor を生成する2つのメソッドが nil を異なる方法で処理していたことにあります。

Tags::Base#add_default_name_and_id_for_value(inputの id 生成に使用)は、tag_valuenil の場合にサフィックスを付与しないため id="user_active" を正しく生成していました。一方、CollectionHelpers#sanitize_attribute_name(labelの for 生成に使用)は常に _ を連結するため、sanitized_value(nil) が空文字列を返しても for="user_active_" という末尾アンダースコア付きの値を生成していました。

修正は actionview/lib/action_view/helpers/tags/collection_helpers.rbsanitize_attribute_name メソッドに加えられました。

変更前:

def sanitize_attribute_name(value)
  "#{sanitized_method_name}_#{sanitized_value(value)}"
end

変更後:

def sanitize_attribute_name(value)
  sanitized = sanitized_value(value)

  if sanitized.empty?
    sanitized_method_name.dup
  else
    "#{sanitized_method_name}_#{sanitized}"
  end
end

サニタイズ後の値が空文字列(nil を含む)の場合はアンダースコアセパレータを省略し、メソッド名のみを返すよう変更されました。これにより add_default_name_and_id_for_value と同じ挙動になります。

リグレッションテストとして actionview/test/template/form_collections_helper_test.rb に2件のテストが追加されています。それぞれ collection_radio_buttonscollection_check_boxes に対して nil 値を含むコレクションを渡した際に forid が一致することを検証し、また label[for=user_active_] が生成されないことも assert_no_select で明示的に確認しています。

設計判断

add_default_name_and_id_for_value の挙動に sanitize_attribute_name を合わせる という方針が採られました。

nil の特別扱いを sanitize_attribute_name 内に閉じ込めることで、呼び出し側のロジックを変更せずに修正が完結しています。sanitized_value(nil) が空文字列を返す事実を活用し、nil を特別ケースとして明示的にチェックするのではなく empty? を条件とすることで、nil 以外で空文字列になる値(例:空文字列のコレクション値)も同様に正しく処理されます。また sanitized_method_name.dup として新しいStringオブジェクトを返している点も、参照の安全性を考慮した実装です。

まとめ

2012年の導入以来残っていた sanitize_attribute_namenil 処理の不整合が、最小限の変更で修正されました。nil 値を含むコレクションでラベルクリックが機能しないというアクセシビリティ上の問題が解消され、collection_radio_buttonscollection_check_boxes の両方で一貫した動作が保証されます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
cf817906

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論→各論→結論」の構成が明確です。リード文で要旨を述べ、背景、技術詳細、設計判断、まとめと、論理的な流れで記事が構成されています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```ruby:ファイルパス)や、コミットID・PR番号のリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Railsの内部実装に関するトピックを扱っており、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で書かれています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションとパラグラフが「総論→各論」の構造で書かれており、トピックセンテンスが明確なため非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事で引用されているコード変更前後のスニペットは、提供されたDiff情報と正確に一致しています。ファイルパスも正しく記載されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`sanitize_attribute_name`や`add_default_name_and_id_for_value`などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

バグの根本原因(2つのメソッドのnil処理の違い)や修正ロジックの説明が技術的に正確であり、PR Descriptionの内容と一致しています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(2012年から存在したバグであること、テストが追加されたことなど)は、PRのDescriptionやDiff内容で裏付けられています。ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57209)、コミットID(36cb715)などの数値・固有名詞は正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトル(Fix label `for` not matching input `id` when collection value is nil)の内容を正確に反映し、要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に含まれない外部知識(バージョンのLTSやEOLなど)の記述はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「2012年から継続していた」という時間表現は、PR Descriptionの記述と一致しており、時間的な歪曲はありません。