ActiveRecord::Base.with がブロック時に Object#with へ委譲
ActiveRecord::Base.with がブロックを受け取ったとき、従来の CTE 用デリゲートではなく Object#with を呼び出すよう変更されました。これにより、モデルクラスの属性を一時的に変更するユースケースがシンプルに記述可能になります。
背景
モデルクラスにカスタムアクセサが定義されている場合、テストや一時的な設定変更をブロックで囲みたいという要望がありました。従来は ActiveRecord::Base.with が常に ActiveRecord::Relation#with に委譲し、ブロックを受け付けないため Object#with が利用できませんでした。PR #57693 はこのギャップを埋め、ブロックが渡されたときに Object#with が呼ばれるように設計されています。結果として、テスト中にロガーを差し替えるような一時的設定が自然な Ruby 記法で実現できるようになりました。
技術的な変更
メソッド一覧からの除外
ActiveRecord::Querying に含まれる QUERYING_METHODS 配列から :with が除外されました。これにより、with が ActiveRecord::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 の拡張性と一貫性を保ちつつ、実務でのテスト支援や一時的設定に有用です。