`LookupContext` の定数ミュータビリティをクラスivarへ移行してRactor対応を前進させる

rails/rails

LookupContext::Accessors::DEFAULT_PROCS をフリーズ不可能な定数から管理可能なクラスインスタンス変数へ移行し、Ractor対応に向けたイミュータブル定数整備の取り組みを継続しました。

背景

RailsをRactorで利用するためには、Ractor間で共有されるクラス・モジュールの定数がすべてShareableである必要があります。#57323Style/MutableConstant Copを有効化し、リテラル定数を広くフリーズする対応が進められましたが、DEFAULT_PROCS はその時点で rubocop:disable コメントで除外されていました。DEFAULT_PROCSregister_detail が呼ばれるたびに新しいエントリが追加されるミュータブルなHashであり、単純にフリーズすることができなかったためです。本PRはその制約を設計で解決する後続の変更です。

技術的な変更

DEFAULT_PROCS 定数を廃止し、同等の情報をフリーズされたクラスインスタンス変数 default_procs として管理するよう変更されました。新しいエントリを追加する際は、既存のHashをコピー・マージして新たなフリーズ済みHashを再代入する方式を採用しています。

変更前:

singleton_class.attr_accessor :registered_details
self.registered_details = []

def self.register_detail(name, &block)
  registered_details << name
  Accessors::DEFAULT_PROCS[name] = block
  # ...
end

module Accessors
  DEFAULT_PROCS = {} # rubocop:disable Style/MutableConstant
end

変更後:

singleton_class.attr_accessor :default_procs
self.default_procs = {}.freeze

def self.registered_details
  self.default_procs.keys
end

def self.register_detail(name, &block)
  self.default_procs = self.default_procs.merge(name => block).freeze
  # ...
end

登録済み詳細の一覧として使われていた配列 registered_details も、default_procs のキーから導出するよう変更されています。これにより、default_procs が「プロシージャの格納場所」と「登録済み詳細の一覧」という2つの役割を1つのデータ構造で担うことになり、クラスインスタンス変数が1つ削減されました。

DEFAULT_PROCS を参照していた内部メソッドも、LookupContext.default_procs を参照するよう更新されています。

# detail_args_for_any 内
- details[k] = Accessors::DEFAULT_PROCS[k].call
+ details[k] = LookupContext.default_procs[k].call

# initialize_details 内
- target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
+ target[k] = details[k] || LookupContext.default_procs[k].call

設計判断

「更新のたびにフリーズ済みの新Hashへ置き換える」パターン が採用されました。

register_detail はアプリケーション起動時に少数回だけ呼ばれる操作であり、その都度 merge で新たなHashを生成してもコストは無視できます。一方で、生成されたHashは以後読み取り専用として扱えるため、Ractor間で安全に共有できる構造になります。ミュータブルな定数を rubocop:disable で黙認し続けるのではなく、データ構造のライフサイクル(起動時の書き込み・実行時の読み取り)に合わせてイミュータブル性を保証した判断といえます。

また、Accessors::DEFAULT_PROCS という名前空間をまたいだ定数参照をやめ、LookupContext.default_procs というクラスメソッドに一本化したことで、アクセスパスが明確になっています。

まとめ

ミュータブルな定数を「コピーしてフリーズ・再代入する」クラスivarへ置き換えることで、Ractor対応の制約を満たしながら内部の状態管理を簡素化しています。起動時のみ変更されるデータを実行時にフリーズ済みとして扱うこのパターンは、Ractor対応を進めるRailsの他の箇所にも応用されていく可能性があります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
0c91d533

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という3部構成が明確に適用されています。各セクションが適切な役割を果たしており、非常に分かりやすい記事構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:ファイルパス)やPR番号のリンク記法([#57323](URL))など、カスタムMarkdown構文がガイドライン通りに正しく使用されています。

対象読者への適合性 ✓ PASS

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

Ractor、ミュータビリティ、クラスインスタンス変数といった専門用語を前提知識として使用しており、専門知識を持つエンジニアという対象読者に完全に適合しています。過度な説明はありません。

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

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

各セクション、各パラグラフが総論→各論の構造を持ち、トピックセンテンスが先頭に置かれているため、非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

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

記事内に引用されている変更前後のコードや、参照箇所の更新例は、提供されたDiff情報と正確に一致しています。ファイル名も正しく記載されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「定数ミュータビリティ」「クラスインスタンス変数(class ivar)」「イミュータブル」などの技術用語が、PRの文脈に沿って正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

「更新のたびにフリーズ済みの新Hashへ置き換える」という変更内容の説明は、`merge`と`freeze`を用いたコード実装を技術的に正しく解説しています。説明に誤りはありません。

事実の突合 ✓ PASS

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

記事内の主張はすべてPRのDescriptionやDiffの内容で裏付けられています。Ractor対応の背景、`rubocop:disable`の経緯、クラスivarへの置換など、ハルシネーションは見られません。

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

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

記事中で言及されているPR番号(#57444, #57323)は正確です。その他の固有名詞にも誤りはありません。

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

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

記事のタイトルは、PRの「何を(`LookupContext`の内部実装を)」「なぜ(Ractor対応のために)」「どのように(クラスivarへ移行して)」変更したのかを的確に要約しており、PR内容と完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事には、PR情報に記載のないバージョン情報やリリース予定などの外部知識は含まれておらず、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

「後続の変更」「継続しました」といった時間表現は、このPRが先行するPRのフォローアップであるという事実と正確に一致しています。