💎

【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は相対参照で定数を探す際、以下の順序で探索します。

  1. 現在のスコープ(内側のクラスやモジュール)
  2. 外側のスコープ(親のモジュールやクラス)
  3. トップレベル(最上位)

絶対参照ではこの順序に関係なく、常にトップレベルを参照します。

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と定数解決ルールの組み合わせを理解しておけば、潜在的なトラブルを予測しやすくなると考えます。

参考

https://docs.ruby-lang.org/en/master/syntax/modules_and_classes_rdoc.html#label-Constants

https://railsguides.jp/autoloading_and_reloading_constants.html

Discussion