🎉

Ruby の .class_eval でクラス変数は参照できない?

2024/06/01に公開

例えばクラスのインスタンス変数は以下のように .class_eval 経由で参照することができます。

class X
  @value = 42
end

pp X.class_eval { @value }

これと同じ様にしてクラス変数にアクセスしようとしたらエラーになりました。

class X
  @@value = 42
end

# error: class variable access from toplevel (RuntimeError)
pp X.class_eval { @@value }

これだとトップレベルでクラス変数を参照しようとしてエラーになっているんですよね。
なので試しに他のメソッドから呼ぶようにしてみたら

class X
  @@value = 42
end

class Y
  @@value = -1234
  def hoge
    X.class_eval {
      # これは Y のクラス変数を参照する
      @@value
    }
  end
end

pp Y.new.hoge
# => -1234

って感じでクラス変数は .class_eval のコンテキスト内に依存するのではなくて呼び出しもとのコンテキストに依存するぽいんですよね。
なので動的にメソッドを定義する際にうっかりクラス変数を参照すると意図しない挙動になります。

class X
  @@value = 42
end

class Y
  @@value = -1234
  def hoge
    X.define_method(:foo) {
      # 本来は X のクラス変数を参照したいが、実際にや Y のクラス変数を参照してしまっている
      @@value
    }
  end
end

Y.new.hoge
pp X.new.foo
# => -1234

クラス変数自体そもそも使わないほうがいいんですが、これは知らなかった。
どうしてもクラス変数を参照したい場合は以下のように .class_variable_get を経由すると取得することができます。

class X
  @@value = 42
end

class Y
  @@value = -1234
  def hoge
    X.define_method(:foo) {
      X.class_variable_get(:@@value)
    }
  end
end

Y.new.hoge
pp X.new.foo
# => 42
GitHubで編集を提案

Discussion