Keep `HashWithIndifferentAccess#filter` returning indifferent access

rails/rails

Rails 8 で HashWithIndifferentAccess の非破壊的メソッド #filterHash を返し、インデファレントアクセスが失われていた問題を解消し、#select と同様に HashWithIndifferentAccess を返すようにしました。

背景

HashWithIndifferentAccess はキーを文字列・シンボルどちらでも参照できる便利なラッパーですが、#filterHash#select のエイリアスとして実装されていなかったため、HashWithIndifferentAccess 上で呼び出すと標準の Hash#filter が呼び出され、結果が普通の Hash になるケースがありました。この挙動は以下のようにインデファレントアクセスが失効します。

h = ActiveSupport::HashWithIndifferentAccess.new(a: 1, b: 2)

h.select { |_, v| v > 1 }.class # => ActiveSupport::HashWithIndifferentAccess
h.filter { |_, v| v > 1 }.class # => Hash   # 旧挙動

結果として h.filter(... )[:b]nil になるなど、期待した API 体験が損なわれていました。

技術的な変更

activesupport/lib/active_support/hash_with_indifferent_access.rbalias_method :filter, :select を追加し、#filter をオーバーライド済みの #select にエイリアスしました。これにより、#filter は内部で dup.tap { |hash| hash.select!(*args, &block) } を実行し、戻り値は常に HashWithIndifferentAccess のインスタンスになります。

@@
 def select(*args, &block)
   return to_enum(:select) unless block_given?
   dup.tap { |hash| hash.select!(*args, &block) }
 end
+alias_method :filter, :select

同時にテスト activesupport/test/hash_with_indifferent_access_test.rb が追加され、#filter が正しく HashWithIndifferentAccess を返すことと、キーアクセスがインデファレントに機能することが検証されています。

@@
 def test_indifferent_filter
   hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).filter { |k, v| v == 1 }

   assert_equal({ "a" => 1 }, hash)
   assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
 end

設計判断

この修正は 「メソッドの振る舞いは同一クラス内で一貫すべき」 という設計方針に基づいています。Ruby 本体では Hash#filterHash#select のエイリアスとして実装されているため、Rails でも同様のエイリアスを提供することで API の予測可能性を高めました。さらに、#filter! / #select! はミュータブル版として既に Hash から継承しているため、追加のオーバーライドは不要です。alias_method による実装はコード量を最小限に抑え、既存の #select ロジックを再利用する安全かつ保守しやすいアプローチです。

まとめ

HashWithIndifferentAccess#filterHashWithIndifferentAccess を返すようになり、インデファレントアクセスが一貫して利用できるようになりました。エイリアス追加のみで既存挙動と整合性を取ったこの変更は、Rails のハッシュユーティリティの信頼性を向上させ、開発者が期待通りにキーを扱える環境を提供します。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
81255129

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文・背景・技術的変更・設計判断・まとめが全て揃っており、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ✓ PASS

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

コードブロックは正しい `ruby:ファイルパス` 形式で記述され、GitHubリンクも `[#57902](URL)` の形で正しくリンク化されています。

対象読者への適合性 ✓ PASS

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

対象はRailsエンジニア向けで、専門用語が中心。初心者向けの余計な説明はありません。

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

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

各セクションが総論・各論・結論の段落で構成され、段落はトピックセンテンスで始まり、1段落1トピック、長さも適切です。空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供されたDiffと完全に一致し、追加された `alias_method :filter, :select` とテストケースが正しく反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語(indifferent access、alias_method、select! など)はPRと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

技術的説明はPRの記述と合致し、因果関係も論理的に正しいです。

事実の突合 ✓ PASS

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

全ての主張がPRタイトル・説明・Diffで裏付けられており、ハルシネーションは検出されません。

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

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

PR番号 #57902 が正しく記載されています。数値の誤りはありません。

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

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

記事タイトルはPRタイトルと完全に一致しています。

外部知識の正確性 ✓ PASS

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

PRに記載されていない外部知識(バージョンサポート等)は一切含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現はなく、PRの記述と矛盾する箇所もありません。