Active Record のスコープを Ractor 共有可能に
Active Record の scope メソッドが Ractor 環境で安全に使用できるよう、共有可能な Proc に変換する変更が加えられました。これにより、マルチスレッド・マルチプロセス構成でスコープロジックをそのまま利用できます。
背景
これまで Ractor 上で Active Record のスコープを呼び出すと、スコープ本体が保持する Proc が共有不可能となり Ractor::IsolationError が発生していました。スコープはモデルクラスのクエリロジックを再利用する重要な手段であり、Ractor の導入が進む Rails アプリケーションでの利用が阻害されていました。したがって、スコープ定義を Ractor でも安全に扱えるようにする必要がありました。
この問題への対処として、Rails コアチームは ActiveSupport::Ractors.try_shareable_proc を利用し、Proc を Ractor 共有可能な形に変換するアプローチを採用しました。変更は ActiveRecord::Scoping::Named モジュールの scope メソッド内部に限定され、既存の API 仕様は一切変更されません。テストも追加され、Ractor 環境での正しい動作が検証されています。
技術的な変更
scope メソッドでは、拡張モジュールを生成する部分を Module.new(&block).freeze に置き換え、生成されたモジュールを不変にしました。これは Ractor がオブジェクトの共有に対して不変性を要求するための前提条件です。続いて、スコープ本体が Proc である場合は ActiveSupport::Ractors.try_shareable_proc(&body) を呼び出し、共有可能な Proc (scope_body) を取得しています。取得した scope_body は _exec_scope に渡され、従来と同様の動作を保ちつつ Ractor 互換性が確保されています。
スコープの実装は、proc do |*args| ... end というラッパー Proc に包まれ、最終的に ActiveSupport::Ractors.try_shareable_proc(&scope_method) で再度共有可能化されます。その結果、singleton_class.define_method(name, &scope_method) によりクラスメソッドとして登録されます。従来の define_method にブロックを直接渡す形から変更はなく、内部ロジックだけが Ractor 対応に変わっています。
テストコード activerecord/test/cases/scoping/named_scoping_test.rb では、共有可能な Proc で定義したスコープが例外なしで呼び出せること、そして共有不可能な Proc が Ractor 環境で使用された際に Ractor::IsolationError が発生することを確認しています。テストは ActiveSupport::Ractors.with(unshareable_proc_action: :raise) コンテキスト内で実行され、期待通りの例外挙動を検証しています。これにより、実装の正当性が自動テストで保証されています。
設計判断
ActiveSupport::Ractors.try_shareable_proc を利用して Proc をラップする方針は、既存の scope API を維持しつつ Ractor 互換性を提供する最小侵襲の設計です。新たに別名の設定やオプションを導入せず、内部でのみ Proc の変換を行うことで、利用者側のコード変更は不要です。さらに、モジュールの freeze により不変オブジェクトとして安全に共有できる点が設計上の重要ポイントです。
この実装は、Ractor が要求するオブジェクトの不変性と共有可能性を尊重しながら、Active Record のスコープ機能をそのまま活かすことを可能にします。非共有可能な Proc が使用された場合は、意図的に例外を発生させて早期に問題を検出できるため、開発者にとっても安全性が向上します。結果として、Ractor を活用したアプリケーションでも従来通りのスコープ記法が利用できるようになりました。
まとめ
この PR では、Active Record の scope メソッドが Ractor 環境でも安全に動作するよう、Proc を共有可能化しモジュールを freeze する変更が加えられました。既存の API を保持しつつ、テストで挙動を裏付けているため、導入時のリグレッションリスクは低くなっています。今後、Ractor を活用した Rails アプリケーションでスコープをそのまま利用できることが期待されます。