Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a module constant also visible within an eigenclass?

Tags:

I created a module that contains a constant NAME and a method hello. If a class includes the module, both definitions should be visible in different scope.

module A
  NAME = 'Otto'
  def self.included(base)
    base.extend(ClassMethods)
  end

  def hello(name = 'world')
    self.class.hello(name)
  end

  module ClassMethods
    def hello(name = 'world')
      "Hello #{name}!"
    end
  end
end

class B
  include A

  def instance_scope
    p [__method__, hello(NAME)]
  end

  def self.class_scope
    p [__method__, hello(NAME)]
  end

  class << self
    def eigen_scope
      p [__method__, hello(NAME)]
    end
  end
end

B.new.instance_scope
B.class_scope
B.eigen_scope

#=> script.rb:34:in `eigen_scope': uninitialized constant Class::NAME (NameError)
    from script.rb:41

But the the constant isn't visible in the instance method scope of the eigenclass, class << self.

Is there a way to make the module more robust and provide the constants also in the errorneous scope above?

like image 552
sschmeck Avatar asked Jan 13 '17 09:01

sschmeck


1 Answers

Solution

class << self
  def eigen_scope
    p [__method__, hello(self::NAME)]
    #=> [:eigen_scope, "Hello Otto!"]
  end
end

Why does self::NAME work?

  • A::NAME would be the easiest, hard-coded version.
  • B::NAME would also work, because B includes A
  • Inside eigen_scope, self is B, so self::NAME works as well
  • self::NAME would also work in self.class_scope
  • self::NAME wouldn't work in instance_scope : a B instance is not a class/module.

Why doesn't NAME work?

Here's a very good explanation.

constant lookup searches for constants that are defined in Module.nesting, Module.nesting.first.ancestors, and Object.ancestors if Module.nesting.first is nil or a module

self is the same in class_scope and eigen_scope.

Module.nesting is different though :

  • [B] for class_scope
  • [#<Class:B>, B] for eigen_scope

So Module.nesting.first.ancestors is :

  • [B, A, Object, Kernel, BasicObject] for class_scope
  • [#<Class:B>, A::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] for eigen_scope

A isn't searched, but A::ClassMethods!

So you could define :

module A
  module ClassMethods
    NAME = 'Bob'
  end
end
like image 78
Eric Duminil Avatar answered Sep 21 '22 10:09

Eric Duminil