Given a User class:
class User
end
I want to define a new constant using .class_eval
. So:
User.class_eval { AVOCADO = 'fruit' }
If I try to access it via User::AVOCADO
, I get uninitialized constant User::AVOCADO
, but User.const_get(:AVOCADO)
works. Why?
If I define a constant inside a Rails Concern in the included
method and include the concern in the User
class, I can access it via the regular ::
lookup. For instance:
module FruitConcern
extend ActiveSupport::Concern
included do
AVOCADO = 'fruit'
end
end
class User
include FruitConcern
end
User::AVOCADO
=> 'fruit'
However, looking up the source code for ActiveSupport::Concern, included
just stores that block in an instance variable (@_included_block
), and then it's override of append_features
will call base.class_eval(&@_included_block)
.
So, if it's just calling User.class_eval
with the same block, why User::AVOCADO
works when the constant is defined inside that included
block, but not when I call User.class_eval { AVOCADO = 'fruit' }
directly?
User.class_eval { AVOCADO = 'teste' }
, it seems Ruby also leaks the constant to the top level. So:User.const_get(:AVOCADO)
=> "fruit"
AVOCADO
=> 'fruit'
I know blocks have flat scopes, but the constant was NOT defined beforehand, and I expected class_eval
to change both self
and the default definee
to the receiver. What is going on here? And how is this constant being defined twice, at the top-level and also in the User scope?
When you define constants you're not assigning a constant to self. You're defining a constant in the current module nesting.
When you explicitly open a class or module you're also setting the module nesting:
module Foo
BAR = 1
puts Module.nesting.inspect # [Foo]
end
When you do User.class_eval { AVOCADO = 'fruit' }
the module nesting is "Main" aka the global object:
User.class_eval do
ADVOCADO = 'Fruit'
puts Module.nesting.inspect # []
end
Blocks do not actually change the module nesting. const_set
on the other hand defines a constant inside of another module nesting.
Ruby also isn't actually defining the constant twice. Rather when you use const_get or reference a constant without explitly using the scope resolution operator Ruby will look through the module nesting and walk the tree up to the global scope:
class A
end
B = 'eureka'
A.const_get(:B) # 'eureka'
Thats how you can reference top level constants without prefacing them with ::
. But when you use User::ADVOCADO
you're explicitly referencing the constant inside of User
.
When it comes to the example with the concern you have missunderstood whats happening. Is not at all about class_eval
. You're defining the constant FruitConcern::AVOCADO
.
When you then include FruitConcern
into User
you're adding FruitConcern
onto the ancestors chain which is included in constant lookup:
module FruitConcern
ADVOCADO = 'fruit'
end
class User
include FruitConcern
end
User::ADVOCADO # fruit
See:
There are three implicit contexts in Ruby:
self
, the "current object": the implicit receiver and the scope for instance variables.def bar
without an explicit target (i.e. not def foo.bar
).The first two are explained very well in the linked article. An article for the third one is promised in the linked article, but it never appeared. Many people have written many words about constants, but unfortunately, nobody has written an authoritative specification, similar to what yugui did for the default definee.
So, unfortunaly, I, too can only speculate, and thus unhelpfully add to the mountain of non-authoritative words written about this topic.
Blocks are lexically scoped in Ruby, and they capture their lexical environment. In general, that means that references inside the block mean exactly the same thing as outside the block, as if the block wasn't there. (The exception are of course block local variables.)
So,
foo { AVOCADO = 'fruit' }
means the same thing as
AVOCADO = 'fruit'
Unless, of course, foo
somehow changes the context in which the block is evaluated. We know that instance_eval
changes self
. We know that class_eval
changes self
and the default definee. However, the important thing is: class_eval
does not change the implicit constant scope.
Hence, the assignment AVOCADO = 'fruit'
inside
User.class_eval { AVOCADO = 'fruit' }
has exactly the same meaning as it would have, if it were outside the block:
AVOCADO = 'fruit'
In other words, it has the same meaning as a constant assignment at the top-level, and as we know, at the top-level:
self
is the unnamed singleton object, commonly called main,Object
, with the added twist that methods become private
by default, andObject
.So, AVOCADO
gets defined in Object
.
Which means that the following works:
class User
AVOCADO
end
#=> 'fruit'
Because constant lookup goes first lexically "outward" (where it fails in this case), then "upward" in the inheritance hierarchy, where it succeeds, because User
implicitly is a subclass of Object
.
User.const_get(:AVOCADO)
#=> 'fruit'
also works, because that's actually the same thing as the one before, just done dynamically with reflection: it starts the constant lookup algorithm in class User
, same as if you had written
class User
AVOCADO
end
However, this does not work:
User::AVOCADO
Which, to be honest, is confusing, because AVOCADO
should be inherited from Object
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With