`Array#to_query` が空ハッシュを含む場合の二重 `&&` を修正
配列に空ハッシュが含まれると Array#to_query が && を二重出力していたバグを、filter_map による空文字列の除去で修正しました。これにより、WHATWG URL 標準に準拠した正しいクエリ文字列が生成されます。
背景
Array#to_query は、配列要素をそれぞれ to_query に変換して & で結合していましたが、空ハッシュを含む場合に不正なクエリ文字列を生成していました。
具体的には、{a: [1, {}, 2]}.to_query を呼び出すと、空ハッシュ {} の to_query が空文字列 "" を返すため、& の間に何もない "a%5B%5D=1&&a%5B%5D=2" が出力されていました。WHATWG URL 標準の application/x-www-form-urlencoded パーサーは & で分割する際、2つの & の間の空文字列を空の name/value ペアとして解釈するため、これはパース上の誤動作を引き起こします。
なお、Hash#to_query はトップレベルで空ハッシュをすでにスキップする実装になっており、今回の修正はその挙動と一貫性を持たせるものです。
技術的な変更
activesupport/lib/active_support/core_ext/object/to_query.rb の Array#to_query 実装が、collect から filter_map を使った実装に変更されました。
変更前:
collect { |value| value.to_query(prefix) }.join "&"
変更後:
filter_map { |value|
q = value.to_query(prefix)
q unless q.empty?
}.join "&"
各要素の to_query 結果を変数 q に受け取り、空文字列であれば nil を返すことで filter_map が自動的に除外します。結合前のリストから空要素が取り除かれるため、& が連続することがなくなります。
テストは activesupport/test/core_ext/object/to_query_test.rb に追加され、{a: [1, {}, 2]}.to_query が "a%5B%5D=1&a%5B%5D=2" を返すことを検証しています。
設計判断
filter_map による除外 という最小限の変更で修正が実現されています。
空ハッシュのケースだけを特別扱いするのではなく、to_query が空文字列を返すあらゆるケースを汎用的に除外しています。nil.to_query は "" を返すため、配列に nil が含まれる場合も同様に除外される動作となります。既存の Array#to_query が空配列の場合に早期リターンするコードパスはそのままに、通常パスのみ変更されており、変更の局所性が保たれています。
まとめ
本修正は、Hash#to_query が持つ「空を読み飛ばす」という既存の挙動を Array#to_query にも一貫して適用した変更です。collect から filter_map への差し替えだけで、WHATWG URL 標準への準拠を確保しつつ、APIインターフェースへの影響を最小化しています。