Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are constants from extended module not available in class methods declared with self.?

Tags:

ruby

I thought there were no differences between methods declared within a class << self block and those declared with a self. prefix, but there are:

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

Why are constants of A available in m1 but not in m2?

like image 978
Andrei Botalov Avatar asked May 07 '13 13:05

Andrei Botalov


1 Answers

In Ruby, constant lookup is not the same as method lookup. For method lookup, calling foo is always the same as calling self.foo (assuming it isn't private). Calling a constant FOO is very different from self::FOO or singleton_class::FOO.

Using an unqualified constant (e.g. FOO) will do a lookup in the currently opened modules. A module is opened with module Mod, class Klass, class << obj, or module_eval and variants. When defining m1, these are B, and then B.singleton_class. When defining m2, only B is opened.

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

In this code, Foo::Bar.hello will return 42, even though X is not a constant of Bar, its singleton class or ancestor. Also, if you later add a constant X to Bar, then that value will be returned. Finally, the following definition is not equivalent:

module Foo
  X = 42
end

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

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

Indeed, when hello is defined, only the class Foo::Bar is opened, while in the previous example, both Foo and Foo::Bar where opened.

A last example, to show the difference an explicit scope can have with inheritance:

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

In your case, you probably want to include your module, instead of extending it, no?

Otherwise, you could use singleton_class::VAR, your code will work as you expect it.

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
like image 114
Marc-André Lafortune Avatar answered Sep 23 '22 02:09

Marc-André Lafortune