`cattr_accessor`/`mattr_accessor`からクラスインスタンス変数へ:Railsの内部実装整理

rails/rails

Railsの内部実装で広く使われていたcattr_accessor/mattr_accessorが、クラスインスタンス変数(singleton_class.attr_accessorまたはclass_attribute)へ段階的に置き換えられています。クラス変数の意味論的な問題を排除し、より予測可能な属性管理を実現する継続的なリファクタリングです。

背景

Rubyのクラス変数@@var)は、継承ツリー全体で共有されるという独自の意味論を持ち、一般的なユースケースでは意図しない動作を引き起こしやすいことが知られています。cattr_accessor/mattr_accessorはこのクラス変数を内部で使用しており、サブクラスでの値の変更が親クラスや他のサブクラスに影響するという問題があります。

参照PR #42442 では、クラス変数のパフォーマンス上の問題も報告されています。同PRのベンチマークによると、クラス変数の読み取りはシングルトン属性と比較して、祖先クラスのない状態で約1.66倍、62の祖先クラスを持つActiveRecord::Baseのような場合にはさらに低速になることが示されています。クラス変数の探索は継承チェーンを遡る必要があるため、祖先の数に比例してコストが増加します。

本PRはその流れを継続するフォローアップとして、ActionMailbox、ActionPack、ActiveRecord、ActiveStorage、ActiveSupportの各コンポーネントにわたるcattr_accessor/mattr_accessorの使用箇所をまとめて整理しています。

技術的な変更

変更のパターンは対象属性の用途によって3種類に分類されます。それぞれ異なる置き換え戦略が採用されている点が特徴的です。

パターン1:class_attributeへの置き換え

サブクラスで上書き可能な属性にはclass_attributeが採用されています。actionmailbox/lib/action_mailbox/routing.rbrouteractionpack/lib/abstract_controller/callbacks.rbraise_on_missing_callback_actionsactionpack/lib/action_controller/metal/strong_parameters.rbpermit_all_parametersaction_on_unpermitted_parametersactivesupport/lib/active_support/cache.rbloggerraise_on_invalid_cache_expiration_timeがこのパターンで置き換えられています。

変更前(actionpack/lib/action_controller/metal/strong_parameters.rb):

cattr_accessor :permit_all_parameters, instance_accessor: false, default: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false

変更後:

class_attribute :permit_all_parameters, instance_accessor: false, default: false
class_attribute :action_on_unpermitted_parameters, instance_accessor: false

パターン2:singleton_class.attr_accessorへの置き換え

モジュールレベルやクラスレベルで単純にクラスインスタンス変数として保持すればよい属性には、singleton_class.attr_accessorが使われています。activesupport/lib/active_support.rbtest_ordertest_parallelization_thresholdparallelize_test_databasesutc_to_local_returns_utc_offset_times、およびactivesupport/lib/active_support/json/decoding.rbparse_json_timesがこのパターンです。

変更前(activesupport/lib/active_support.rb):

cattr_accessor :test_order
cattr_accessor :test_parallelization_threshold, default: 50
cattr_accessor :parallelize_test_databases, default: true

変更後:

singleton_class.attr_accessor :test_order

@test_parallelization_threshold = 50
singleton_class.attr_accessor :test_parallelization_threshold

@parallelize_test_databases = true
singleton_class.attr_accessor :parallelize_test_databases

デフォルト値はcattr_accessordefault:オプションに頼らず、インスタンス変数への直接代入で明示されるようになっています。

パターン3:class << self; attr_accessorへの置き換え

テストモデルクラスのような箇所では、class << selfブロック内のattr_accessorで置き換えられています。activerecord/test/models/developer.rbinstance_countactiverecord/test/models/membership.rbcurrent_memberがこれにあたります。

# 変更前
cattr_accessor :instance_count

# 変更後
class << self
  attr_accessor :instance_count
end

DateAndTime::Compatibilityモジュールの削除

注目すべき付随変更として、activesupport/lib/active_support/core_ext/date_and_time/compatibility.rbが完全に削除されています。このファイルはmattr_accessor :utc_to_local_returns_utc_offset_timesを提供するためだけに存在していましたが、この属性がActiveSupportモジュール自体のsingleton_class.attr_accessorに移動したことで不要になりました。DateTimeTimeからもDateAndTime::Compatibilityincludeが除去されています。

設計判断

class_attribute vs singleton_class.attr_accessorの使い分けが本PRの核心的な設計判断です。

class_attributeActiveSupportが提供する機能で、サブクラスで独立して値を上書きできる継承対応の属性を定義します。ActionController::Baseのサブクラスがpermit_all_parametersを独自に変更しても他のコントローラに影響しない、という動作が期待される箇所に適用されています。一方、ActiveSupportモジュール自体の設定値や、継承ツリーを通じた共有が不要な箇所では、よりシンプルなsingleton_class.attr_accessorが選ばれています。

activesupport/lib/active_support/cache.rbではclass_attributeへの移行に伴い、テストコードのヘルパーもActiveSupport::Cache::Store.logger = ...というクラスレベルの一時変更から、@cache.with(logger: ...)というインスタンスレベルのスコープ付き変更に書き直されています。これにより、クラス変数のグローバルな状態変更を伴うテストのセットアップ・テアダウンが不要になり、テストの独立性が向上しています。

activesupport/lib/active_storage/blob.rbではscope_for_strict_loading内のstrict_loading_by_default?呼び出しにself.が明示的に付加されました。これはclass_attributeが生成するメソッドを確実にレシーバ付きで呼び出すための修正であり、変更の副産物として生まれた精緻化です。

まとめ

このPRは、クラス変数の意味論的な問題とパフォーマンス特性を踏まえ、用途に応じた適切な属性管理機構へ移行するRails内部のリファクタリングを前進させるものです。置き換えパターンを3種類に分類して適用することで、外部APIの互換性を維持しながら、内部実装をRubyの標準的なイディオムに近づけています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
27c1522b

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の構成が明確に適用されています。各セクションの役割も適切です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きのシンタックスハイライト(```ruby:path/to/file.rb)や、PR番号のリンク記法([#42442])が正しく使用されています。

対象読者への適合性 ✓ PASS

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

`cattr_accessor`やクラスインスタンス変数といったRails/Rubyの内部的なトピックを扱っており、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクション、各パラグラフが「総論→各論」の構造で書かれています。また、各段落はトピックセンテンスで始まり、1段落1トピックが守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードスニペットは、提供されたDiff情報と完全に一致しています。ファイルパスの指定も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「クラス変数」「クラスインスタンス変数」「singleton_class」「class_attribute」といった技術用語が、文脈に応じて正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

クラス変数が継承ツリーで共有される問題点や、`class_attribute`と`singleton_class.attr_accessor`の使い分けに関する説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事の主張はすべてPRのTitle, Description, Diff内容で裏付けられています。特に、参照PR(#42442)のパフォーマンスに関する記述も、本PRの文脈を説明するために適切に引用されており、ハルシネーションは見られません。

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

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

PR番号(#56986, #42442)や、参照PRから引用したパフォーマンス数値(約1.66倍)は正確です。

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

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

記事のタイトル「`cattr_accessor`/`mattr_accessor`からクラスインスタンス変数へ:Railsの内部実装整理」は、PRの主題を的確に表現しています。

外部知識の正確性 ✓ PASS

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

PR情報に記載のないバージョンサポート情報やリリース日程などの外部知識は含まれておらず、すべての情報が提供された資料に基づいています。

時間表現の正確性 ✓ PASS

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

「段階的に置き換えられています」「継続的なリファクタリング」といった時間表現は、PRの「Followup」「Further reduce...」という文脈と一致しており、正確です。