Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion about ruby class variable

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.

like image 232
dj199008 Avatar asked Feb 20 '14 12:02

dj199008


People also ask

What is class variable in Ruby?

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.

Are class variables inherited Ruby?

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.

Does Ruby have static variables?

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.


2 Answers

@@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
like image 52
Arup Rakshit Avatar answered Oct 16 '22 02:10

Arup Rakshit


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.

like image 44
Cary Swoveland Avatar answered Oct 16 '22 02:10

Cary Swoveland