Ruby Looks for Class Variable in the Object Instead of Specific Class

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 class instance variable vs. class variable

Instance variable on a class:

class Parent
@things = []
def self.things
@things
end
def things
self.class.things
end
end

class Child < Parent
@things = []
end

Parent.things << :car
Child.things << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things #=> [:doll]
p mom.things #=> [:car]
p dad.things #=> [:car]

Class variable:

class Parent
@@things = []
def self.things
@@things
end
def things
@@things
end
end

class Child < Parent
end

Parent.things << :car
Child.things << :doll

p Parent.things #=> [:car,:doll]
p Child.things #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

With an instance variable on a class (not on an instance of that class) you can store something common to that class without having sub-classes automatically also get them (and vice-versa). With class variables, you have the convenience of not having to write self.class from an instance object, and (when desirable) you also get automatic sharing throughout the class hierarchy.


Merging these together into a single example that also covers instance variables on instances:

class Parent
@@family_things = [] # Shared between class and subclasses
@shared_things = [] # Specific to this class

def self.family_things
@@family_things
end
def self.shared_things
@shared_things
end

attr_accessor :my_things
def initialize
@my_things = [] # Just for me
end
def family_things
self.class.family_things
end
def shared_things
self.class.shared_things
end
end

class Child < Parent
@shared_things = []
end

And then in action:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things << :vacuum
mama.shared_things << :car
papa.shared_things << :blender
papa.my_things << :quadcopter
joey.my_things << :bike
suzy.my_things << :doll
joey.shared_things << :puzzle
suzy.shared_things << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things #=> [:house, :vacuum]
p papa.family_things #=> [:house, :vacuum]
p mama.family_things #=> [:house, :vacuum]
p joey.family_things #=> [:house, :vacuum]
p suzy.family_things #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things #=> [:car, :blender]
p mama.shared_things #=> [:car, :blender]
p Child.shared_things #=> [:puzzle, :blocks]
p joey.shared_things #=> [:puzzle, :blocks]
p suzy.shared_things #=> [:puzzle, :blocks]

p papa.my_things #=> [:quadcopter]
p mama.my_things #=> []
p joey.my_things #=> [:bike]
p suzy.my_things #=> [:doll]

Confusion about ruby class variable

@@var is a class variable of Holder. And the @@var at the top level is not the same class variable of same name @@var of Holder, it is you are creating a brand new class variable for the class Object. Now @@var is shared with the subclass(es) of the parent class. Object is a parent class of the class Holder. In Ruby, if you don't explicitly define a super class of any custom class, you are defining using class keyword, then Object class becomes the default super class of the class, you just created.

On top level, you are defining a new class variable in the class Object, like you did, in your posted example. As through inheritance, it is now available to the class Holder.

I mean, when you wrote the below :

class Holder
@@var = 99
def Holder.var=(val)
@@var = val
end
def var
@@var
end
end

@@var is not added already to the Object class. Now on the top-level when you wrote the below line :

@@var = "top level variable"

It means, you are adding it to Object class and also updating old variable(@@var) same name as in the Holder class one, with this new one, you just defined in the scope of Object class.

Remember, class variables are shared variables and visible only to the class(B), where you defined it and its descendant class(C), descendant(D) of the descendant class(C), so on. But not visible by the super-class( A as below ) of the class( B as below ), until you defined a same name variable in the parent class(A), as you are having in the child class(B).

class A 
end

class B < A
@@var = 10
end

class C < B
end

class D < C
end

A.class_variable_get(:@@var) # uninitialized class variable @@var in A (NameError)
B.class_variable_get(:@@var) # 10
C.class_variable_get(:@@var) # 10
D.class_variable_get(:@@var) # 10

How to access variables from an extended class in ruby

Yes they can, and here's a snippet that shows how.

class A2
def initialize
@foo = 42
end
end

class B2 < A2
def print_foo
puts @foo
end
end

# Prints 42
B2.new.print_foo

The above code defines a class A2, with a constructor that defines and sets an instance variable @foo. Class B2 extends A2, and defines a method that uses @foo.

I think the issue with your code is that @property is not assigned a value, as the assignment isn't in a method that gets called at any point.

Is a Ruby global variable equivalent to a class variable on Class Object?

1) JS: My understanding is that variables set outside the "outer" function's scope are global in context

This depends on what exactly you mean by "variables". Variables declared with const or let are lexically scoped.

Variables declared with var in the top-level context aren't actually variables at all, they become properties of the top-level object (e.g. window, document, global, … depending on your environment).

and store a location to the assigned value.

That is true of all variables in both Ruby and ECMAScript, as well as properties in ECMAScript.

3) "Everything" in Ruby is an object (except blocks and individual index items within an array), including classes, primitives, etc.

This really depends on what exactly you mean by "everything". There are lots of "objects" (in the sense of "things we can talk about") in Ruby that aren't "objects" (in the sense of "things we can assign to variables, pass around, manipulate in Ruby code"). For example, variables aren't objects in Ruby.

A) Is the Rails.application object actually an instance of the Application Class?

I have no idea what this has to do with global variables.

Is there any difference between a Ruby global variable:

$var = "A Ruby global variable"

and

Class Object
@@var = "A class variable set on the Class Object"
end

Yes, there is the pretty obvious difference that objects and classes which don't have Object in their ancestors chain won't have access to Object's class variables:

class BasicObject
@@var
end
# NameError: uninitialized class variable @@var in BasicObject

Is Class Object, as the Class from which all other Classes/Objects ultimately inherit, therefore, Ruby's "global" context (or not, and I missed something huge)?

The Object class acts as a sort-of global context for some stuff, but that is not because it is global, but rather because it is the parent of most classes (basically anything that doesn't extend directly from BasicObject).

Anything that uses inheritance (class variables, methods, and constants) and is defined in Object will also be available in everything that descends from Object. But that has nothing to do with "global". That's just how inheritance works. Note that Object itself inherits from Kernel and BasicObject, so this is true for anything defined in those two as well.

C) (added as an edit) Because a global variable needs to be initiated and is available in a global scope, it is a class variable and not an instance variable. Is that reasoning sound?

No, it is not. A global variable is neither an instance variable nor a class variable. It is a global variable.

If a global variable is an object, it has to inherit from Class Object, right? This would mean the "global" context is still wrapped within Class Object.

Variables aren't objects in Ruby.



Related Topics



Leave a reply



Submit