1

Consider the following code:

c1 = Class.new { ANSWER = 42 }
#⇒ #<Class:0x00556c8fc09c60> < Object
c1.constants
#⇒ []
c1.new.class.constants
#⇒ []
c1.new.singleton_class.constants
#⇒ []
Object.constants.grep /ANS/
#⇒ [:ANSWER]

The constant seems to be defined on the Object. But with the explicit call to const_set the constant assignment works perfectly:

c2 = Class.new { const_set :ANSWER, 42 }
#⇒ #<Class:0x00556c8f7f3568> < Object
c2.constants
#⇒ [:ANSWER]
c2.new.class.constants
#⇒ [:ANSWER]
c2.new.singleton_class.constants
#⇒ [:ANSWER]

My question is: what prevents the constant from being properly assigned on Class.new instance in the first snippet?

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • Similar: https://stackoverflow.com/questions/10078744/accessing-a-constant – Marcin Kołodziej Sep 27 '18 at 04:34
  • @MarcinKołodziej seems like the [last answer there](https://stackoverflow.com/a/10078954/2035262) sheds a light on he issue and the lexical scope matters here. We seem to need an explicit `class` keyword to open the new scope. – Aleksei Matiushkin Sep 27 '18 at 04:39
  • Which is honestly insane, but taking for granted `define_method` works similar contrary to explicit `def`, maybe that’s done on purpose. – Aleksei Matiushkin Sep 27 '18 at 04:40
  • 2
    `Class.new { self::ANSWER = 42 }` does the trick, or `Class.new { |c| c::ANSWER = 42 }` if you want to be more explicit. – Stefan Sep 27 '18 at 09:36

2 Answers2

5

There are three implicit contexts in Ruby:

  • self (the context for receiverless message sends, and instance variables)
  • the default definee (the context for def method definition expressions without an explicit target, i.e. def bar instead of def foo.bar)
  • the default constant definition point

Unfortunately, while the article I linked to above lists all three of them, it only discusses the first two and defers the default constant definition point to a later article, which was never written.

Anyway, it is important to keep those three contexts in mind, and be aware of when they change and when they don't.

In particular, a block only changes the lexical context and nothing else. A block does not change self, it does not change the default definee and it does not change the default constant definition point.

However, there are some methods whose explicit purpose it is to change one or more of those three contexts.

The *_eval family of methods changes self (context #1) and the default definee (context #2), but it does not change the default constant definition point (context #3). In particular, all *_eval (*_exec) methods set self to the receiver. The instance_* versions set the default definee to the receiver's singleton class, the module_* and class_* versions set the default definee to the receiver.

However, the default constant definition point is not changed, so constant definition (and lookup) works just as before: definitions go to the nearest lexically enclosing module definition, lookup starts at the nearest lexically enclosing module definition and proceeds lexically outwards and dynamically upwards-by-inheritance.

As far as I could find, the only construct that changes the default constant definition point is a module/class definition.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
1

The doc for Class::new states, "If a block is given, it is passed the class object, and the block is evaluated in the context of this class like class_eval.", meaning that

c = Class.new { ANSWER = 42 }

is equivalent to

c = Class.new
c.class_eval  { ANSWER = 42 }

class_eval creates the constant at top-level, because it was called on c at top-level. c.class_eval { ANSWER = 42 } is in fact the same as

ANSWER = 42

which creates a constant on Object.

Object::ANSWER
  #=> 43

Here it is the other way around.

NUMBER = 43
Object::NUMBER
  #=> 43

c = Class.new
c.const_set(:NUMBER, 48)
c::NUMBER
  #=> 48
c.class_eval { puts NUMBER }
43

Here's another example.

class F
  class G; end
  G.class_eval { HIPPO = 5 }
end

F::HIPPO
  #=> 5
F::G::HIPPO
  #=> #NameError (uninitialized constant F::G::HIPPO)
  #   Did you mean?  F::HIPPO

However,

class F
  class G
    class_eval { HIPPO = 5 }
  end
end

F::HIPPO
  #=> NameError (uninitialized constant F::HIPPO)
F::G::HIPPO
  #=> 5

Readers: see Module#class_eval and this discussion about constant lookup using class_eval (the complement of creating a constant). (Search for "class_eval").

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • _“and the block is evaluated in the context of this class ”_ —is exactly why I would expect `ANSWER` to be defined on `c`, not on `Object`. – Aleksei Matiushkin Sep 27 '18 at 05:31