Почему константы из расширенного модуля недоступны в классах, объявленных с помощью self.?

Я думал, что не было различий между методами, объявленными в блоке class << self, и теми, которые были объявлены с префиксом self., но есть:

module A
  VAR = 'some_constant'
end

class B
  extend A

  class << self
    def m1
      puts VAR
    end
  end

  def self.m2
    puts VAR
  end
end

B.m1 # => OK
B.m2 # => uninitialized constant B::VAR

Почему константы A доступны в m1, но не в m2?

Ответы

Ответ 1

В Ruby постоянный поиск не совпадает с поиском метода. Для поиска метода вызов foo всегда совпадает с вызовом self.foo (при условии, что он не является приватным). Вызов константы foo сильно отличается от self::FOO или singleton_class::FOO.

Использование неквалифицированной константы (например, foo) будет выполнять поиск в открытых модулях. Модуль открывается с помощью module Mod, class Klass, class << obj или module_eval и вариантов. При определении m1 это B, а затем B.singleton_class. При определении m2 открывается только B.

module Foo
  X = 42
  class Bar
    def self.hello
      X
    end
  end
end

В этом коде Foo::Bar.hello вернет 42, хотя X не является константой Bar, ее одноэлементным классом или предком. Кроме того, если позднее вы добавите константу X в Bar, это значение будет возвращено. Наконец, следующее определение не эквивалентно:

module Foo
  X = 42
end

class Foo::Bar
  def self.hello
    X
  end
end

Foo::Bar.hello # => uninitialized constant Foo::Bar::X

В самом деле, когда hello определен, открывается только класс Foo::Bar, а в предыдущем примере открываются как foo, так и Foo::Bar.

Последний пример, чтобы показать разницу, которую явная область может иметь с наследованием:

class Base
  X = 42
  def self.foo
    X
  end
  def self.bar
    self::X
  end
end

class Parent < Base
  X = :other
end

Parent.foo # => 42
Parent.bar # => :other

В вашем случае вы, вероятно, захотите include вашего модуля, а не extending он, no?

В противном случае вы можете использовать singleton_class::VAR, ваш код будет работать так, как вы ожидаете.

module A
  VAR = 'some_constant'
end

class B
  extend A

  class << self
    def m1
      puts singleton_class::VAR # not necessary here, as singleton_class is opened
    end
  end

  def self.m2
    puts singleton_class::VAR # necessary here!
  end
end

B.m1 # => OK
B.m2 # => OK