Ruby constants in singleton methods

170 Views Asked by At

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.

1

There are 1 best solutions below

7
Aleksei Matiushkin On BEST ANSWER

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.