views:

37

answers:

2

Why there is an error in the following example?

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

Is there another way to create ClassE, not by typing class ClassA; class ClassE?

+1  A: 

Well, yes, if you define your test method to return ClassA::ClassB.new :-)

You could also play around with const_missing so that it calls ClassA.const_get.

Otherwise ClassB is not in the current scope, which at that point is only ClassA::ClassE and Object. When you first open ClassA, then ClassE, the lookup for ClassB is done first in ClassA::ClassE, then in ClassA (where it is found) and would also look in Object.

Marc-André Lafortune
Is there a way to get the namespace path? Something like `[:ClassA, :ClassE]`?
Andrey
Good question. I don't think the language gives us access to that.
Marc-André Lafortune
There is a workaround - look at my answer below.
Andrey
@Andrei: ah, right. I thought your question was, is there a way that `class A; class B; puts magic end end` would output `[A::B, A, Object]`, while `class A::B; puts magic end` would output `[A::B, Object]`.
Marc-André Lafortune
A: 

The custom Object#const_missing method, suggested by Marc-André Lafortune, would be then

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

Some related questions:

  1. Does Ruby provide the namespace path, e.g. something like [:A,:B] for class A::B::C?
  2. If I define a class-method in Ruby Object class, how do I get the name of a child class calling this method?
  3. How do I get class-object from string “A::B::C” in Ruby?
Andrey