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
Why Is the Splat Used Inside an Array Definition Here
Ruby: Cannot Install Watir Gem on Windows
How to Run an Excel MACro from Ruby
Rails Cancan and State MAChine - Authorizing States
How to Mock Super in Ruby Using Rspec
Using Form_For Tag with Get Method
Can't Run Ruby 2.2.3 with Rvm on Osx
Remote Form_Tag in Rails3 Without a Named Route
Read and Write File Atomically
Fast-Stemmer Installation Problems
Ruby on Rails Active Admin Has_Many Changing Dropdown to Use a Different Column
Nested Form_For Singular Resource
How to Include Ё in [А-Я] Regexp Char Interval
Differencebetween Unicorn and Unicorn_Rails
Are There More Elegant Ways to Prevent Negative Numbers in Ruby
Ruby: Automatically Set Instance Variable as Method Argument