Class Variables in Ruby

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]

Why is using a class variable in Ruby considered a 'code smell'?

As you can find in their documentation on Class Variables:

Class variables form part of the global runtime state, and as such make it easy for one part of the system to accidentally or inadvertently depend on another part of the system. So the system becomes more prone to problems where changing something over here breaks something over there. In particular, class variables can make it hard to set up tests (because the context of the test includes all global state).

Essentially, it's a manifestation of global state, which is almost universally considered evil, because it makes tests more difficult and results in a much more fragile class/program structure.

This Stack Overflow question may also be worth reading, which shows the main problem with class variables: if any class inherits from your class and modifies the class variable, every instance of that variable changes, even from the parent! This understandably gives you a way to shoot yourself in the foot easily, so it may be best to avoid them unless you're very careful.

It's also worth comparing class variables with class instance variables. This question has a few good examples which illustrate the usage differences, but in essence class variables are shared, whereas class instance variables are not shared. Therefore, to avoid unwanted side effects, class instance variables are almost always what you want.

Initialize class object variable in Ruby

In Ruby, @@ before a variable means it's a class variable. What you need is the single @ before the variable to create an instance variable. When you do Result.new(..), you are creating an instance of the class Result.

You don't need to create default values like this:

@@min = 0
@@max = 0

You can do it in the initialize method

def initialize(min = 0, max = 0)

This will initialize min and max to be zero if no values are passed in.

So now, your initialize method should like something like

def initialize(min=0, max=0)
@min = min
@max = max
end

Now, if you want to be able to call .min or .max methods on the instance of the class, you need to create those methods (called setters and getters)

def min # getter method
@min
end

def min=(val) # setter method
@min = val
end

Now, you can do this:

result.min     #=> 1
result.min = 5 #=> 5

Ruby has shortcuts for these setters and getters:

  • attr_accessor: creates the setter and getter methods.
  • attr_reader: create the getter method.
  • attr_writer: create the setter method.

To use those, you just need to do attr_accessor :min. This will create both methods for min, so you can call and set min values directly via the instance object.

Now, you code should look like this

class Result
attr_accessor :min, :max
def initialize(min=0, max=0)
@min = min
@max = max
end
end

result = Result.new(1, 10)
result.max #=> 10

Difference between class variables and class instance variables?

A class variable (@@) is shared among the class and all of its descendants. A class instance variable (@) is not shared by the class's descendants.


Class variable (@@)

Let's have a class Foo with a class variable @@i, and accessors for reading and writing @@i:

class Foo

@@i = 1

def self.i
@@i
end

def self.i=(value)
@@i = value
end

end

And a derived class:

class Bar < Foo
end

We see that Foo and Bar have the same value for @@i:

p Foo.i    # => 1
p Bar.i # => 1

And changing @@i in one changes it in both:

Bar.i = 2
p Foo.i # => 2
p Bar.i # => 2

Class instance variable (@)

Let's make a simple class with a class instance variable @i and accessors for reading and writing @i:

class Foo

@i = 1

def self.i
@i
end

def self.i=(value)
@i = value
end

end

And a derived class:

class Bar < Foo
end

We see that although Bar inherits the accessors for @i, it does not inherit @i itself:

p Foo.i    # => 1
p Bar.i # => nil

We can set Bar's @i without affecting Foo's @i:

Bar.i = 2
p Foo.i # => 1
p Bar.i # => 2

instance and class variables in rails controller

Classes are also objects in Ruby, so they can have their own instance variables which are called class instance variables.

  • @@world is a class variable
  • @insworld is a class instance variable
  • #index is an instance method

When you try to access @insworld in #index, Ruby searches for the instance variable in the A object (meaning A.new) because #index is an instance method.

But you defined @insworld as a class instance variable which means it is defined in the class object itself (meaning A).

The following code demonstrates:

class Hi
@@a = 1 # class variable
@b = 2 # class instance variable

def initialize
@c = 3 # instance variable
end

def test # instance method, works on objects of class Hi
puts @@a # => 1
puts @b # => nil, there is no instance variable @b
puts @c # => 3 # we defined this instance variable in the initializer
end
end

Hi.class_variables # => @@a
Hi.instance_variables # => @b
Hi.new.instance_variables # => @c
# Hi is an object of class Class
# Hi.new is an object of class Hi

Keep in mind that all instance variables return nil if they don't exist.

How do I access class variable?

In Ruby, reading and writing to @instance variables (and @@class variables) of an object must be done through a method on that object. For example:

class TestClass
@@variable = "var"
def self.variable
# Return the value of this variable
@@variable
end
end

p TestClass.variable #=> "var"

Ruby has some built-in methods to create simple accessor methods for you. If you will use an instance variable on the class (instead of a class variable):

class TestClass
@variable = "var"
class << self
attr_accessor :variable
end
end

Ruby on Rails offers a convenience method specifically for class variables:

class TestClass
mattr_accessor :variable
end


Related Topics



Leave a reply



Submit