Ruby Dynamic Classes. How to fix warning: class variable access from toplevel
Just to remove this warning, you should use class_variable_set
method:
x = [1,2,3]
Test = Class.new do
class_variable_set(:@@mylist, x)
def foo
puts @@mylist
end
end
How to set a class variable from a class-level method when using Class.new?
The two pieces are not identical, there are differences in lexical scoping of the declarations.
In the first definition, the more common class declaration, definitions are lexically scoped to class A
, so the class variable is set on A. Only A and classes that inherit from A
will have that class variable. Since Class
does not inherit from A, it doesn't get the class variable.
In the second definition, the dynamic class assignment, the definitions are lexically scoped to top level object, which is object. So a class variable will be set on Object
, the class of the top level object.
Now, this has huge implications. Every object inherits from Object and every class is an instance of the class Class
, you're defining a class variable on each and every class in your object space. Hence, A gets the class variable, and Class
gets it as well. Try defining a new class F
, and it will have it as well. This is why Ruby screams the warning:
class variable access from toplevel
This is one of reasons why class variables are typically avoided.
There are multiple ways to solve this. My favourite is, using the attr_accessor
on the class instance:
K = Class.new do
class << self
attr_accessor :fruit
end
self.fruit = :banana
end
K.fruit
# => :banana
# The value isn't shared between inherited classes,
# but the variable is:
class L < K
end
L.fruit
# => nil
L.fruit = :mango
# => :mango
K.fruit
# => :banana
Edit:
Keep in mind, that these variables are still not thread-safe, and are shared by all threads. You will need thread-local variables for ensuring thread-safety.
Scope of class variable
This code appears in both rb_cvar_set
and rb_cvar_get
in MRI's variable.c
:
if (front && target != front) {
st_data_t did = id;
if (RTEST(ruby_verbose)) {
rb_warning("class variable %"PRIsVALUE" of %"PRIsVALUE" is overtaken by %"PRIsVALUE"",
QUOTE_ID(id), rb_class_name(original_module(front)),
rb_class_name(original_module(target)));
}
if (BUILTIN_TYPE(front) == T_CLASS) {
st_delete(RCLASS_IV_TBL(front),&did,0);
}
}
id
is the C-internal representation of the variable name (@@foo
).
front
is the class in which the variable is currently being accessed (B
/C
).
target
is the most distant ancestor in which the variable has also ever been defined (A
).
If front
and target
are not the same, Ruby warns that class variable #{id} of #{front} is overtaken by #{target}
.
The variable name is then literally deleted from front
's RCLASS_IV_TBL, so that on subsequent lookups, the search for that variable name "falls through" or "bubbles up" to the most distant ancestor in which the variable is defined.
Note that this check and deletion happen not just on cvar gets, but on sets as well:
$VERBOSE = true
module A; end
class B; include A; @@foo = 1; end # => 1
module A; @@foo = 3 end # => 3
class B; p @@foo = 1 end # => 1
#=> warning: class variable @@foo of B is overtaken by A
module A; p @@foo end # => 1
In this example, even though it's A
's value of 3
being overwritten by the value 1
being set in B
, we still receive the same warning that it's B
's class variable being overtaken by A
!
While it is usually more surprising to the average Ruby coder to find that the value of their variable is changing in various, perhaps unexpected, places (i.e. in "parent"/"grandparent"/"uncle"/"cousin"/"sister" modules and classes), the trigger and the wording both indicate that the warning is actually intended to inform the coder that the variable's "source of truth" has changed.
Ruby: Unexpected Results from class_exec When Defining Class Variable
class variables are bound to the class in which they are declared at compile time. The block passed to class_exec
is compiled before it is passed to class_exec
, so the class variables are bound to Object
.
I guess your class_exec is at the top level, which is in Object, so that's where they go. To demonstrate:
public
class Object
@@x = "ribbit"
end
def foo
puts "test: #{@@x}"
end
x = Object.new
x.foo
This is why when you use class vars in a module, all classes that include that module (through the included methods) will see the same class variables. The class variables are bound to the module. If you run this:
class WithClassVars
def self.classvars
@classvars ||= {}
end
def classvars
self.class.classvars
end
end
class A < WithClassVars;end
class B < WithClassVars;end
a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2
puts a.classvars
puts b.classvars
a and b will end up with the same data.
If you pass your code as a string to class_eval
, the string is compiled in class_eval
, so you can make sure they are in the right class then.
So if you want to store per-class data, you have to either go with class_eval, or use some mechanism to use a class's instance variables. Say:
class WithClassVars
def self.classvars
@classvars ||= {}
end
def classvars
self.class.classvars
end
end
class A < WithClassVars;end
class B < WithClassVars;end
a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2
puts a.classvars
puts b.classvars
Delegate to class variable
You can initiaize @@classB
inside a class method and then refer to this class method:
class A
extend Forwardable
def self.b
@@classB ||= B.new
end
def_delegator 'self.class.b', :method_name, :a_method_name
end
Why is `instance_eval`/`class_eval` not able to access class variables?
The error thrown is because MySelf.instance_eval('@@name')
correctly throws an error. This is not an instance variable, it's a class variable. You'll want to have MySelf.class_eval('@@name')
on it's own, and then it'll work.
Check the repl here: https://repl.it/Be0U/0
To set the class variable, use class_variable_set
like so:
MySelf.class_variable_set('@@name', 'graham')
how to access a class variable of outer class from inner class in ruby
The only way to access this class variable is via an accessor method
class A
def self.lock
@@lock ||= Monitor.new
end
class B
def method
A.lock.synchronize
puts "xxxxx"
end
end
end
end
class_eval and context of class variables?
I've had to experience some weak moment, as the answer is quite clear & obvious.
From the Module.class_eval
documentation:
Evaluates the string or block in the context of mod, except that when a
block is given, constant/class variable lookup is not affected. This can be
used to add methods to a class. module_eval returns the result of evaluating
its argument.
So if I would need directly access class variables from eval block (ie. without use of class variable getter/setter methods), I'd just pass the code as a string:
MyClass.class_eval <<-EOS
p @@myvar # class variable lookup is working from a string
# output: 123 voila!
EOS
Related Topics
Can't Get Rack-Cors Working in Rails Application
How to Run Rails Console in the Test Environment and Load Test_Helper.Rb
One Liner in Ruby for Displaying a Prompt, Getting Input, and Assigning to a Variable
Rails Search with Optional Parameters
How Does Defining [Square Bracket] Method in Ruby Work
Hashes of Hashes Idiom in Ruby
Ruby on Rails: Devise, Want to Add Invite Code
Rspec: How to Test If a Method Was Called
Cannot Execute "Rails Console" Due to an Error with Readline
How to Handle Errors with Httparty
How to Use Dot Syntax for Ruby Hash
How to Create a Full Audit Log in Rails for Every Table
How to Loop Over a Hash of Hashes
Why Bundle Install Is Installing Gems in Vendor/Bundle