Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Ruby, in a method defined in class << self, why can't a constant defined on the superclass be access without self?

Tags:

ruby

I'm trying to understand Ruby singletons and class inheritance better. I read everywhere that

def self.method_name; end`

is equivalent to

class << self
  def method_name; end
end

But if that were true, then I would expect print_constant_fails to work, but it doesn't. What is going on here?

class SuperExample
  A_CONSTANT = "super example constant"
end

class SubExample < SuperExample
  def self.print_constant_works_1
    puts A_CONSTANT
  end
  class << self
    def print_constant_works_2
      puts self::A_CONSTANT
    end
    def print_constant_fails
      puts A_CONSTANT
    end
  end
end
pry(main)> SubExample.print_constant_works_1
super example constant

pry(main)> SubExample.print_constant_works_2
super example constant

pry(main)> SubExample.print_constant_fails
NameError: uninitialized constant #<Class:SubExample>::A_CONSTANT
from (pry):13:in `print_constant_fails'
like image 846
pduey Avatar asked Jul 22 '21 20:07

pduey


People also ask

How do you define a constant in Ruby?

How to Define Constants. A constant doesn't require any special symbol or syntax to declare. You just need to make the first letter an uppercase letter.

What is self class in Ruby?

Class Method Self A class method is a method that refers only to that class in all contexts, but not to any individual instances of that class. A class instance method is a method that applies to all instances of that class, but not for the class object itself.

What is difference between module and class in Ruby?

What is the difference between a class and a module? Modules are collections of methods and constants. They cannot generate instances. Classes may generate instances (objects), and have per-instance state (instance variables).

What is super class in Ruby?

Ruby uses the super keyword to call the superclass implementation of the current method. Within the body of a method, calls to super acts just like a call to that original method. The search for a method body starts in the superclass of the object that was found to contain the original method.

What is the use of Def self method in Ruby?

When we use def self.method, though, we are defining a method across scopes: we are present in the regular class scope, but we use Ruby’s ability to define methods upon specific instances from anywhere; self within a class definition is the Class instance we are working on (i.e. the class itself).

What is a class method in Ruby?

Since in Ruby classes are objects as well, class methods are merely methods defined on a specific instance of Class. Consider the following example: We can see that theory in action easily: Example.is_a?

What is a singleton class in Ruby?

When a singleton method is created, Ruby automatically creates an anonymous class to store that method. These anonymous classes are called metaclasses, also known as singleton classes or eigenclasses. The singleton method is associated with the metaclass which, in turn, is associated with the object on which the singleton method was defined.

What is the “self” keyword in Ruby?

If you’re learning Ruby you may find the use of the “self” keyword very confusing. How does it work? What is self, exactly? It’s a Ruby keyword that gives you access to the current object. If you don’t know what objects are, watch this video I made for you.


2 Answers

You have encountered a common Ruby gotcha - constant lookup.

The most important concept in constant lookup is Module.nesting (unlike in method lookup, where the primary starting point is self). This method gives you the current module nesting which is directly used by the Ruby interpreter when resolving the constant token. The only way to modify the nesting is to use keywords class and module and it only includes modules and classes for which you used that keyword:

class A
  Module.nesting #=> [A]

  class B
    Module.nesting #=> [A::B, A]
  end
end

class A::B
  Module.nesting #=> [A::B] sic! no A
end

In meta programming, a module or class can be defined dynamically using Class.new or Module.new - this does not affect nesting and is an extremely common cause of bugs (ah, also worth mentioning - constants are defined on the first module of Module.nesting):

module A
  B = Class.new do
    VALUE = 1
  end

  C = Class.new do
    VALUE = 2
  end
end

A::B::VALUE #=> uninitialized constant A::B::VALUE
A::VALUE #=> 2

The above code will generate two warnings: one for double initialization of constant A::VALUE and a second for reassigning the constant.

If it looks like "I'd never do that" - this also applies to all the constants defined within RSpec.describe (which internally calls Class.new), so if you define a constant within your rspec tests, they are most certainly global (unless you explicitly stated the module it is to be defined in with self::)

Now let's get back to your code:

class SubExample < SuperExample
  puts Module.nesting.inspect #=> [SubExample]

  class << self
    puts Module.nesting.inspect #=> [#<Class:SubExample>, SubExample]
  end
end

When resolving the constant, the interpreter first iterates over all the modules in Module.nesting and searches this constant within that module. So if nesting is [A::B, A] and we're looking for the constant with token C, the interpreter will look for A::B::C first and then A::C.

However, in your example, that will fail in both cases :). Then the interpreter starts searching ancestors of the first (and only first) module in Module.nesting. SubrExample.singleton_class.ancestors gives you:

[
  #<Class:SubExample>,
  #<Class:SuperExample>,
  #<Class:Object>,
  #<Class:BasicObject>,
  Class,
  Module,
  Object,
  Kernel,
  BasicObject
]

As you can see - there is no SuperExample module, only its singleton class - which is why constant lookup within class << self fails (print_constant_fails).

The ancestors of Subclass are:

[
  SubExample,
  SuperExample,
  Object,
  Kernel,
  BasicObject
]

We have SuperExample there, so the interpreter will manage to find SuperExample::A_CONSTANT within this nesting.

We're left with print_constant_works_2. This is an instance method on a singleton class, so self within this method is just SubExample. So, we're looking for SubExample::A_CONSTANT - constant lookup firstly searches on SubExample and, when that fails, on all its ancestors, including SuperExample.

like image 177
BroiSatse Avatar answered Nov 15 '22 22:11

BroiSatse


It has to do with scope. When you are inside class << self, the scope is different than when you are inside class Something. Thus, inside class << self there is actually no constant called A_CONSTANT.

like image 33
vaporyhumo Avatar answered Nov 15 '22 23:11

vaporyhumo