in_order_ofでnil要素を保持するよう修正
Enumerable#in_order_of のデフォルト filter:true でも、 series に明示的に nil を含めた場合に nil が除外されていた問題を解消し、 filter:false と同様に nil を保持できるようになった。
背景
filter:true が指定されたとき、in_order_of は内部で group_by(&key).values_at(*series).flatten(1).compact を実行しており、flatten の後に compact が走るため、 series に nil が明示的に含まれていても compact がそれらを除去してしまっていた。これにより、 [3, nil, 1, 2].in_order_of(:itself, [1, nil, 2, 3]) の結果が [1, 2, 3] となり、期待どおりの nil が失われていた。filter:false では別のロジックが使われており nil が正しく保持されていたため、二つのモード間で挙動の不整合が存在した。
技術的な変更
変更点は activesupport/lib/active_support/core_ext/enumerable.rb 内の in_order_of 実装で、compact と flatten の呼び出し順序を入れ替えたことです。
@@
- group_by(&key).values_at(*series).flatten(1).compact
+ group_by(&key).values_at(*series).compact.flatten(1)
compact を先に適用することで、nil 要素が series に含まれるケースでは compact が nil を除去せずに残す(values_at が返す配列の中の nil が対象外になるだけ)という挙動になる。その後の flatten(1) で一次元配列に平坦化しつつ、nil はそのまま保持される。結果として、 filter:true でも nil が正しく出力され、 filter:false と同一の振る舞いが得られる。
テストも activesupport/test/core_ext/enumerable_test.rb に追加され、nil が series に含まれるケースで期待通りの配列が返ることを検証している。
def test_in_order_of_preserves_nil_elements_named_in_series
values = [ 3, nil, 1, 2 ]
assert_equal [ 1, nil, 2, 3 ], values.in_order_of(:itself, [ 1, nil, 2, 3 ])
end
設計判断
この修正は 既存のインターフェイスを変更せず、内部ロジックだけを入れ替える という最小限のアプローチを取っている。filter:true のデフォルト動作は依然として nil 以外の要素を順序付けて返すが、 series に nil が明示的に指定された場合は保持できるようになるため、二つのモード間の一貫性が確保された。また、compact と flatten の順序入れ替えは副作用がなく、既存コードに対する後方互換性も維持される。
まとめ
Enumerable#in_order_of の内部処理を compact → flatten の順に変更したことで、filter:true でも series に含まれる nil 要素が正しく保持され、filter:false と同様の動作が得られるようになった。挙動の統一と最小限の変更という設計判断により、既存ユーザーへの影響はなく、期待通りの結果がテストで保証されている。