ActiveRecord::Base.with がブロック時に Object#with へ委譲

rails/rails

ActiveRecord::Base.with がブロックを受け取ったとき、従来の CTE 用デリゲートではなく Object#with を呼び出すよう変更されました。これにより、モデルクラスの属性を一時的に変更するユースケースがシンプルに記述可能になります。

背景

モデルクラスにカスタムアクセサが定義されている場合、テストや一時的な設定変更をブロックで囲みたいという要望がありました。従来は ActiveRecord::Base.with が常に ActiveRecord::Relation#with に委譲し、ブロックを受け付けないため Object#with が利用できませんでした。PR #57693 はこのギャップを埋め、ブロックが渡されたときに Object#with が呼ばれるように設計されています。結果として、テスト中にロガーを差し替えるような一時的設定が自然な Ruby 記法で実現できるようになりました。

技術的な変更

メソッド一覧からの除外

ActiveRecord::Querying に含まれる QUERYING_METHODS 配列から :with が除外されました。これにより、withActiveRecord::QueryMethods に依存することなく、モジュール内部で独自実装が提供できるようになります。

@@
-      :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with_recursive,
+      :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with_recursive,

with メソッドのオーバーライド

ActiveRecord::Querying に新たに with メソッドが追加され、ブロックの有無で振る舞いを分岐させます。ブロックが渡され、かつ Object#with がロードされている場合は super(= Object#with)へ委譲し、ブロックが無い場合は従来通り all.with(Relation の CTE ビルド)を呼び出します。

@@
-    delegate(*QUERYING_METHODS, to: :all)
+    delegate(*QUERYING_METHODS, to: :all)
+
+    # When a block is given and the core extension Object#with is available,
+    # delegate to it so that attributes can be temporarily set on the class.
+    # Otherwise fall back to the Relation#with implementation for CTE construction.
+    def with(...)
+      return super if block_given? && defined?(super)
+      all.with(...)
+    end

テストの追加

with_test.rb にクラスレベルでブロックを渡した際に Object#with が呼び出され、ブロック終了後に属性が元に戻ることを確認するテストが追加されました。また、Relation に対してブロックを渡すと ArgumentError が発生する既存の制約が引き続き検証されています。

@@
-      def test_raises_when_using_block
+      def test_class_with_calls_object_with_when_given_a_block
         original = Post.attributes_for_inspect

         captured = nil
         result = Post.with(attributes_for_inspect: [:id]) do
           captured = Post.attributes_for_inspect
           :block_value
         end

         assert_equal [:id], captured
         assert_equal :block_value, result
         assert_equal original, Post.attributes_for_inspect
       end

設計判断

この変更は メソッドのオーバーロード と条件分岐だけで実装され、既存 API のシグネチャを保持しつつ新しい振る舞いを提供しています。Object#with はオプションのコア拡張であるため、ロードされていない環境でも super が未定義であることを利用して安全にフォールバックできます。結果として、追加のメソッド名や設定項目を導入することなく、ブロック対応を実現した点が設計上の大きな利点です。

まとめ

ActiveRecord::Base.with がブロックを受け取った場合に Object#with へ委譲するようになり、モデルクラスの属性を一時的に設定できるシンプルな手段が提供されました。従来の CTE 機能はそのまま保持され、API 変更や後方互換性への影響はありません。この設計は Rails の拡張性と一貫性を保ちつつ、実務でのテスト支援や一時的設定に有用です。

記事メタデータ

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

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文があり、背景→技術的変更→設計判断→まとめの流れが明確に構成されている。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きハイライトは正しい形式だが、PRリンクの記法が `[PR #57693](URL)` となっており、要求された `[#57693](URL)` 形式と一致していない。

対象読者への適合性 ✓ PASS

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

内容は Rails エンジニア向けで、余計な初心者向け解説はなく適切な技術レベルになっている。

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

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

各セクションは総論・各論・結論の構成を保ち、段落はトピックセンテンスで始まり、1段落1トピック・長さも適切に保たれている。

Diff内容との照合 ⚠ WARNING

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

`QUERYING_METHODS` から `:with` を除外する変更が Diff には含まれているが、記事のコードブロックではその削除が示されていない。また、テストファイルの `require` 行や一部テスト名の変更が省略されている。重要なロジックは正しく示されているため致命的ではない。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語は PR と一致しており、`Object#with`, `CTE`, `delegate` などの使用も正確です。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の背景・目的・実装と合致し、誤りはない。

事実の突合 ✓ PASS

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

記事の主張はすべて PR の記載内容で裏付けられ、推測や外部知識の付加はない。

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

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

PR 番号 #57693 以外の数値・固有名詞は記載されていない。

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

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

記事タイトルは PR タイトルの意味を日本語で適切に表現している。

外部知識の正確性 ✓ PASS

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

Rails のバージョンサポートやリリース日程等の外部情報は一切含まれていない。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PR の記述と一致している。