💎
【Ruby】"superclass mismatch for class XXX" の発生原因と解決方法
Rubyの定数の参照ルールを理解する
Rubyでは定数を参照する際にスコープによって振る舞いが変わります。
主に以下の2つの方法があります。
-
絶対参照 (
::Customer
のようにトップレベルからの参照) -
相対参照 (
Customer
のように現在のスコープからの参照)
この記事では、両者の挙動の違いを実際のコードを使って解説します。
例で使うオブジェクトの関係性
説明に使う例では、以下の3つのオブジェクトを想定しています。
-
Customer
(顧客):ストアで買い物をするお客様。 -
Order
(注文):顧客がストアで行う注文。 -
Store
(ストア):注文を処理する場所。
これらを使って定数参照を具体的に示していきます。
絶対参照と相対参照の違い
絶対参照はトップレベルから定数を探します。例えば以下のような構造です。
# app/models/customer.rb
class Customer
NAME = 'Top Level Customer'
end
module Store
NAME = 'Store Level'
class Order
def customer_name
::Customer::NAME # 絶対参照
end
end
end
puts Store::Order.new.customer_name
# => "Top Level Customer"
相対参照の場合、現在のスコープから外側へ順番に探します。
class Customer
NAME = 'Top Level Customer'
end
module Store
NAME = 'Store Level'
class Order
def customer_name
NAME # 相対参照
end
end
end
puts Store::Order.new.customer_name
# => "Store Level"
定数探索のルール
Rubyは相対参照で定数を探す際、以下の順序で探索します。
- 現在のスコープ(内側のクラスやモジュール)
- 外側のスコープ(親のモジュールやクラス)
- トップレベル(最上位)
絶対参照ではこの順序に関係なく、常にトップレベルを参照します。
Rails(Zeitwerk)での注意点
RailsアプリケーションではZeitwerkという自動読み込みの仕組みがあります。Zeitwerkは未定義の定数が参照された瞬間、その定数に対応するファイルを自動で読み込みます。
ファイル構造の例:
app/
├── controllers/
│ └── store/
│ └── orders_controller.rb
└── models/
└── customer.rb
定数が初めて参照されるまで、Zeitwerkはファイルをロードしません。
- 絶対参照を使うと、トップレベルの定数が直接指定されるため、予期せぬタイミングでファイルがロードされる可能性があります。
- 相対参照では実際に定数が必要になるまでロードが遅延されます。
具体的なエラー事例(superclass mismatch)
絶対参照を使った場合のエラー例です。
# app/models/customer.rb
class Customer < ApplicationRecord
end
# テストコード(spec/support/mocks/customer.rb)
class Customer
end
絶対参照を使った場合、テストコードより先にモデルのCustomer
がロードされます。その後テストコードが読み込まれると、以下のエラーが発生します。
superclass mismatch for class Customer
相対参照にするとモデルの読み込みが遅延され、エラーを回避できます。
私の考え
Rubyの定数解決ルールはシンプルですが、実際のRailsアプリケーションで絶対参照を多用すると混乱を招きます。定数参照は相対参照に統一し、絶対参照は特別な場合に限定して使うべきと考えます。
絶対参照を使う場合、ロード順序に注意してテスト環境や依存関係を整えましょう。RailsのZeitwerkと定数解決ルールの組み合わせを理解しておけば、潜在的なトラブルを予測しやすくなると考えます。
参考
Discussion