color_field のバリデーションを正確な 6 桁 HEX に限定
color_field が文字列中に 6 桁の HEX が含まれるだけで有効と判定し、ブラウザが解釈できない <input type="color"> を生成していた問題を、正確に "#rrggbb" 形式のみ受け付けるように修正しました。
背景
color_field の内部で使用されている validate_color_string が、以前は /#[0-9a-fA-F]{6}/ という正規表現で文字列中に 6 桁の十六進数が現れるかどうかだけをチェックしていました。その結果、"Not a color #123456 at all" のように HEX が部分文字列として含まれるだけの入力や、8 桁の #rrggbbaa 形式までが誤って有効と判断され、ブラウザが受け付けないカラーコードがそのまま出力されていました。PR の説明にある通り、実際に生成されたマークアップは <input type="color" value="#123456"> のようになり、ユーザー体験を損なうバグでした。
技術的な変更
正規表現のアンカー追加 によって、文字列全体が "#rrggbb" 形式に完全一致するかを検証するように変更しました。具体的な差分は次のとおりです。
@@ -13,7 +13,7 @@ def render
private
def validate_color_string(string)
- regex = /#[0-9a-fA-F]{6}/
+ regex = /\A#[0-9a-fA-F]{6}\z/
if regex.match?(string)
string.downcase
else
この変更により、文字列全体が "#rrggbb" の形でない限りマッチせず、#000000 にフォールバックされます。フォールバックロジックは既存コードと同一で、他の不正な入力(例: "#1234TR")と同様の挙動を保ちます。
テストも新たに追加され、"Not a color #123456 at all" のようなケースで期待通りに #000000 が出力されることが確認されています。
@@ -1147,6 +1147,12 @@ def test_color_field_with_invalid_hex_color_string
assert_dom_equal(expected, color_field("car", "color"))
end
+ def test_color_field_with_string_containing_hex_color_substring
+ expected = %{<input id="car_color" name="car[color]" type="color" value="#000000" />}
+ @car.color = "Not a color #123456 at all"
+ assert_dom_equal(expected, color_field("car", "color"))
+ end
+
def test_color_field_with_value_attr
expected = %{<input id="car_color" name="car[color]" type="color" value="#00FF00" />}
assert_dom_equal(expected, color_field("car", "color", value: "#00FF00"))
設計判断
正規表現のアンカー化 というシンプルな修正で問題を解決した点が本 PR の設計上の選択です。新たにパラメータや設定項目を導入することなく、既存メソッドのロジックだけを変更し、後方互換性を維持しています。代替案としては入力文字列を正規表現以外でパースする、あるいは別のバリデーション層を追加するといった手段も考えられましたが、最小変更で確実に期待動作を得られる今回のアプローチが採択されました。
まとめ
color_field のバリデーションが文字列全体の "#rrggbb" 形式に限定されたことで、ブラウザが解釈できないカラーコードが生成されるケースが排除されました。この修正は正規表現のアンカー化だけで実装され、既存の API と互換性を保ちつつ、入力検証の堅牢性を向上させています。