Почему я получаю сообщение об ошибке при попытке сослаться на вложенный класс в Ruby?

Почему в следующем примере возникает ошибка?

class ClassA
  class ClassB
  end
  class ClassC
    def test
      ClassB.new
    end
  end
end

p ClassA::ClassC.new.test # => #<ClassA::ClassB:0x0000010103f860>

class ClassA
  class ClassD
    def test
      ClassB.new
    end
  end
end

p ClassA::ClassD.new.test # => #<ClassA::ClassB:0x0000010103f010>

class ClassA::ClassE
  def test
    ClassB.new
  end
end

p ClassA::ClassE.new.test # => NameError: uninitialized constant ClassA::ClassE::ClassB

Есть ли другой способ создать ClassE, не набрав class ClassA; class ClassE?


person Andrei    schedule 22.07.2010    source источник


Ответы (2)


Ну да, если вы определите свой тестовый метод так, чтобы он возвращал ClassA::ClassB.new :-)

Вы также можете поиграть с const_missing, чтобы он вызывал ClassA.const_get.

В противном случае ClassB не входит в текущую область действия, которая на данный момент состоит только из ClassA::ClassE и Object. Когда вы сначала открываете ClassA, а затем ClassE, поиск ClassB выполняется сначала в ClassA::ClassE, затем в ClassA (где он находится) и также будет искаться в Object.

person Marc-André Lafortune    schedule 22.07.2010
comment
Есть ли способ получить путь к пространству имен? Что-то вроде [:ClassA, :ClassE]? - person Andrei; 23.07.2010
comment
Хороший вопрос. Я не думаю, что язык дает нам доступ к этому. - person Marc-André Lafortune; 23.07.2010
comment
@Андрей: а, верно. Я думал, ваш вопрос был в том, есть ли способ, которым class A; class B; puts magic end end выводит [A::B, A, Object], а class A::B; puts magic end выводит [A::B, Object]. - person Marc-André Lafortune; 23.07.2010

Пользовательский метод Object#const_missing, предложенный Марк-Андре Лафортюн, тогда

def Object.const_missing(name)
  @looked_for ||= {}
  key = self.to_s + '~' + name.to_s
  raise "Class not found: #{name}" if @looked_for[key] == key
  return @looked_for[key] if @looked_for[key]
  @looked_for[key] = key
  if self.to_s.include? '::'
    klass = Object
    self.to_s.split('::')[0..-2].each do |klass_string|
      klass = klass.const_get klass_string
    end
    return @looked_for[key] = klass.const_get(name) if klass # klass.is_a?(Class)
  end
  raise "Class not found: #{name}"
end

Некоторые связанные вопросы:

  1. Предоставляет ли Ruby путь к пространству имен , например что-то вроде [:A,:B] для класса A::B::C?
  2. Если я определяю метод класса в классе Ruby Object, как мне получить имя дочернего класса, вызывающего этот метод?
  3. Как мне получить класс-объект из строки " A::B::C» в Ruby?
person Andrei    schedule 22.07.2010