Parameters#select と #reject がブロック未指定時に Enumerator を返すように修正
Rails 8.1系の ActionController::Parameters では、select と reject がブロックなしで呼び出された際に例外が発生していたが、Ruby の Hash#select/#reject と同様に Enumerator を返すよう修正された。
背景
ActionController::Parameters#select と #reject は内部のハッシュ (@parameters) をラップしているが、ブロックが与えられない場合に to_enum ガードが欠如していた。その結果、@params.select が裸の Enumerator を返し、続く new_instance_with_inherited_permitted_status へ渡すと NoMethodError: undefined method 'each_key' for an instance of Enumerator がスローされた。
同様のガードは他の Enumerable ラッパー(each_pair、transform_values など)で既に実装されており、一貫性が欠如していた点が問題である。
この不整合を解消し、ブロックなしでも安全に列挙子を取得できるようにすることが本修正の目的である。
技術的な変更
select と reject に block_given? チェックと to_enum ガードを追加し、ブロックが無い場合は即座に列挙子を返すようにした。既存のロジックはブロックが提供されたときだけ実行され、挙動に後方互換性は保たれる。
@@
- def select(&block)
- new_instance_with_inherited_permitted_status(@parameters.select(&block))
- end
+ def select(&block)
+ return to_enum(:select) unless block_given?
+ new_instance_with_inherited_permitted_status(@parameters.select(&block))
+ end
@@
- def reject(&block)
- new_instance_with_inherited_permitted_status(@parameters.reject(&block))
- end
+ def reject(&block)
+ return to_enum(:reject) unless block_given?
+ new_instance_with_inherited_permitted_status(@parameters.reject(&block))
+ end
テストスイートにもブロックなしで Enumerator が返ることを確認するケースが追加された。これにより、select と reject の両方で Enumerator が返り、続く each によって再び ActionController::Parameters インスタンスが生成されることが保証される。
@@
test "reject without a block returns an enumerator" do
assert_kind_of Enumerator, @params.reject
assert_kind_of ActionController::Parameters, @params.reject.each { |k, v| false }
end
@@
test "select without a block returns an enumerator" do
assert_kind_of Enumerator, @params.select
assert_kind_of ActionController::Parameters, @params.select.each { |k, v| true }
end
設計判断
本修正は 既存の Enumerable ラッパーと同様の to_enum ガードを導入 するという最小限の変更に留めた。新たな設定キーや API の追加は行わず、メソッド本体に guard 文を挿入するだけで済むため、後方互換性が確保された。
また、Ruby 標準の Hash と同等の挙動を提供することで、開発者は select.with_index { … } のようなチェーン操作を期待通りに利用でき、API の一貫性が向上した。
まとめ
Parameters#select と Parameters#reject がブロック未指定時に Enumerator を返すようになり、例外が除去されて API の整合性が改善された。実装は guard 文の追加だけで済み、既存コードへの影響はなく、テストによって安全性が検証されている。