Ruby Dynamic Classes. How to Fix "Warning: Class Variable Access from Toplevel"

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



Leave a reply



Submit