'Ruby constants in singleton methods

I have a Ruby C extension; I call that class FooC defined in ext/foo/foo.c, in that file I have

void Init_foo(void)
{
  VALUE cFoo = rb_const_get(rb_cObject, rb_intern("FooC"));
  :
  rb_define_const(cFoo, "BAR", 7);
  :

That is included in the "Ruby part" of the extension in lib/foo.rb like

class FooC ; end

require 'foo/foo'

class Foo < FooC
  class << self
    def puts_bar
      puts BAR
    end
    :
  end
  :
end

Unexpectedly, I get an error

NameError: uninitialized constant #<Class:Foo>::BAR

I was expecting that Ruby would not find BAR in the Foo namespace, but would then search FooC where it would find it. Strangely, if I use Foo::BAR or self::BAR, the error goes away, and a similar usage in a Foo instance method gives me no errors at all.

Anyone have an idea as to why I need this seemingly redundant qualification of constants in singleton methods?

[edit]

Following the interesting answer below, I confirm that this qualification is not needed in a proper class method:

class Foo < FooC
  def self.puts_bar
    # no NameError here
    puts BAR
  end
  :
end

rather than a instance method of the eigenclass.



Solution 1:[1]

You are looking the constant up in the eigenclass of Foo, not in the Foo itself. Start with figuring out what’s what without any bindings.

class FooC; end
class Foo < FooC
  BAR = 42
  puts "Foo: #{self}, DEF: #{const_defined?(:BAR)}"
  class << self
    def puts_bar
      puts "Foo.class: #{self} DEF: #{self.class.const_defined?(:BAR)}"
      puts self.class.const_get(:BAR)
    end
  end
  def puts_bar
    puts "Foo.class: #{self} DEF: #{self.class.const_defined?(:BAR)}"
    puts self.class.const_get(:BAR)
  end
end

And see how it works:

#  Foo: Foo, DEF: true

[2] pry(main)> Foo.puts_bar
#? Foo.class: Foo DEF: false
#   NameError: uninitialized constant Class::BAR
#   from (pry):8:in `const_get'

[3] pry(main)> Foo.new.puts_bar
#? Foo.class: #<Foo:0x0000556e174b91c0> DEF: true
#   42

Constants are made visible within instance functions by looking up the class’ constants. #<Class:Foo> in the error message you’ve got is explicitly pointing out to that.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nakilon