Assume a simple ruby program using a class variable,
class Holder
@@var = 99
def Holder.var=(val)
@@var = val
end
def var
@@var
end
end
@@var = "top level variable"
a = Holder.new
puts a.var
I guess the result should be 99
, but the output is not 99
. I wonder why. Since class variable's scope is the class, I assume the line @@var = "top level variable"
would not affect the variable in the class.
Class variables begin with @@ and must be initialized before they can be used in method definitions. Referencing an uninitialized class variable produces an error. Class variables are shared among descendants of the class or module in which the class variables are defined.
Ruby doesn't have class variables in the sense that, say, Java (where they are called static fields) has them. It doesn't need class variables, because classes are also objects, and so they can have instance variables just like any other object.
In Ruby, there are two implementations for the static keyword: Static Variable: A Class can have variables that are common to all instances of the class. Such variables are called static variables. A static variable is implemented in ruby using a 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
I would like to amplify on what others have said and, in particular, compare the use of class variables with class instance variables. Let's start with this:
class Holder1
end
class Holder2 < Holder1
@@var = 99
# Create a class setter and a getter for the class variable
def Holder2.var=(val)
@@var = val
end
def Holder2.var
@@var
end
# Create a instance getter for the class variable
def var
@@var
end
end
class Holder3 < Holder2
end
Holder2.var #=> 99
Holder2.var = 1
Holder3.var #=> 1
Holder1.var #=> NoMethodError: undefined method `var' for Holder1:Class
Holder2.new.var #=> 1
Holder3.new.var #=> 1
Holder3.var = 2
Holder3.var #=> 2
Holder2.var #=> 2
Holder3.new.var #=> 2
Holder2.new.var #=> 2
Aside: the first two methods would normally be written like this:
def self.var=(val)
@@var = val
end
def self.var
@@var
end
This works because self
=> Holder2
when the methods are created. By not hardwiring the class name, no change is required if you decide to rename the class.
Now this (the use of a class variable) may be exactly the behavior you want. That is, if you want subclasses to see the variable and be able to change it, a class variable is what you need.
If, however, you want each subclass to have their own variable, which can be neither seen nor changed by subclasses, you need to use a class instance variable, @var
, rather than a class variable, @@var
. (Technically, this is not quite right, because one could employ Holder2.instance_variable_get(:@var)
or Holder2.instance_variable_set(:@var)
anywhere in your program.)
Compare the results of the code that follows with that above. I've included an instance variable with the same name as the class instance variable, @var
, to illustrate that they are as different as are @night
and @day
.
class Holder1
end
class Holder2 < Holder1
# Create an accessor for the instance variable
attr_accessor :var
# Initialize class instance variable
@var = 99
# Create an accessor for the class instance variable
class << self
puts "self in 'class << self': #{self}"
attr_accessor :var
def our_dog
"Diva"
end
end
# Create a class method
def self.our_cat
puts "self in 'self.our_cat())' def: #{self}"
"Teagan"
end
# Create an instance setter and a getter for the class instance variable
def c_ivar=(val)
self.class.var = val
end
def c_ivar
self.class.var
end
end
class Holder3 < Holder2
end
#=> self in 'class << self': #<Class:Holder2>
Holder2.var #=> 99
h2 = Holder2.new
h2.var #=> nil
Holder2.var = 1
Holder2.var #=> 1
h2.c_ivar #=> 1
h2.c_ivar = 2
Holder2.var #=> 2
h2.var #=> nil
h2.var = 3
h2.var #=> 3
Holder2.var #=> 2
Holder3.var #=> nil
Holder1.var #=> NoMethodError: undefined method `var' for Holder1:Class
Holder3.var = 4
Holder3.var #=> 4
Holder2.var #=> 2
h3 = Holder3.new
h3.c_ivar #=> 4
h2.c_ivar #=> 2
Holder3.our_dog #=> "Diva"
Holder3.our_cat #=> "self in 'self.our_cat())' def: Holder3"
#=> "Teagan"
Holder1.var
raises an exception because that class has no access to Holder2
's class instance variable var@
. By contrast, the first use of Holder3.var
returns nil
because the variable exists for Holder3
, by inheritance, but has not been initialized.
The expression class << self
changes self
from Holder2
to Holder2
's singleton class (aka metaclass) until the next end
. Notice that Ruby denotes Holder2
's singleton class as #<Class:Holder2>
. Also, we do not need to prepend my_dog
with self.
because self
is Holder2
's singleton class when the method is created.
Notice that:
Holder2.singleton_methods #=> [:var, :var=, :our_dog, :our_cat]
This shows that our_cat()
is a method of Holder2
's singleton class, even though self
was Holder2
(and not Holder2
's singleton class ', #<Class:Holder2>
) when the method was constructed. For this reason, some say that, technically, "there is no such thing as a class method". This also tells us that we could have moved the definition of my_cat
into the class << self
construct (and dropped self.
).
Another common way to add instance variables and methods to Holder2
's singleton class is to replace class << self ... end
with extend M
and create a module:
module M
attr_accessor :var
def our_dog
"Diva"
end
end
Object#extend mixes these instance variables and methods into Holder2
's singleton class.
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