Rails and Class Variables

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.

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]

Rails and class variables

Variables with @ are instance variables in ruby. If you're looking for class variables, they're prefixed with @@, so you should be using @@my_var = 123 instead.

And the reason you can't use instance variables that way, is because if you define instance variables outside methods, they don't live in the same scope as your methods, but only live while your class is interpreted.

var1 in your example is a local variable, which will only be visible inside the index method.

Examples:

class Foo
@@class_variable = "I'm a class variable"

def initialize
@instance_variable = "I'm an instance variable in a Foo class"
local_variable = "I won't be visible outside this method"
end

def instance_method_returning_an_instance_variable
@instance_variable
end

def instance_method_returning_a_class_variable
@@class_variable
end

def self.class_method_returning_an_instance_variable
@instance_variable
end

def self.class_method_returning_a_class_variable
@@class_variable
end
end

Foo.new
=> #<Foo:0x007fc365f1d8c8 @instance_variable="I'm an instance variable in a Foo class">
Foo.new.instance_method_returning_an_instance_variable
=> "I'm an instance variable in a Foo class"
Foo.new.instance_method_returning_a_class_variable
=> "I'm a class variable"
Foo.class_method_returning_an_instance_variable
=> nil
Foo.class_method_returning_a_class_variable
=> "I'm a class variable"

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

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

Redis vs Class variables in Rails

Class variables have their places, if they represent a concept that is unchangeable and should live as a definition of something, like a configuration throughout the application, or some fundamental multiplier of a part of the application domain/business.

But, just because things are unchanging, that doesn't mean they have to be a class variable. You can have an instance of a class to be always setup with the same values, and then share the instance. Kind of like a singleton, but not necessarily being a singleton, just being a widely shared variable that is part of the input on the start of a given process.

So instead of using Sidekiq to store data for this unchanging thing, and rather than using class variables, you can achieve a cleaner design by doing something like

class ImportantThing
def initialize(name, other_property)
@name = name
@other_property = other_property
end

# Other methods you wish to define the behavior of the thing
end

then you can

the_important_thing = ImportantThing.new("foobar", 3.46)
do_important_process(the_important_thing)

then as far as the important process you are running is concerned, the important thing is anything that behaves like a important thing, and it doesn't matter if it is a globally setup or ever unchanging.

This makes for a easy to test architecture which is generally a sign of clear and decoupled design.

Variable available to class methods (within Concerns)

For a quick solution, to make Product.all and Product.visible work with the least amount of modification to your existing code, you can define a parameters method inside module ClassMethods. For example:

def parameters
@parameters ||= [:visible, :desc, :value]
end

This method solution can also serve as a long-term solution if you plan to use the parameters outside of the concern, or if a subclass might want to define its own parameters.

However, if the parameters are only meant to be used inside this concern, and this data will never change, at least not through any application logic, then a constant would be the best solution because it conveys the proper meaning to the reader. I would also freeze it to prevent modification:

PARAMETERS = [:visible, :desc, :value].freeze

Another option, as Rich mentioned, is to define a class variable. Note that the constant will work whether you define it inside the List module, or inside the ClassMethods module. However, a class variable will only work inside the ClassMethods module if you want Product to be able to call it as parameters.

Also, note that self is implied in any method within ClassMethods, so you don't need to specify it. If you defined a parameters method, it would be considered a Product class method, and if you used parameters within the all method, it would refer to the class method, not an instance method as suggested by Rich.

Class variables are generally discouraged in Ruby because their side effects are often misunderstood. The Ruby Style Guide recommends avoiding them: https://github.com/bbatsov/ruby-style-guide#no-class-vars

As for speed, I compared the method and constant solutions, and it looks like the constant is faster:

require "benchmark/ips"

PARAMETERS = [:visible, :desc, :value].freeze

def parameters
@parameters ||= [:visible, :desc, :value]
end

def uses_constant
puts PARAMETERS
end

def uses_method
puts parameters
end

Benchmark.ips do |x|
x.report("constant") { uses_constant }
x.report("method") { uses_method }
x.compare!
end

The result:

Comparison:
constant: 45256.8 i/s
method: 44799.6 i/s - 1.01x slower

why cannot reassign class variables in controller of rails

This is an extension of what @lurker has mentioned in the comments to the question. It does not matter whether you use a class variable or a class level instance variable, you will see the same behaviour.

However what happens behind the scenes is different when you use the two types of variables.

Case 1 : Class variable (i.e. @@var)

The value of the class variable is being set during class declaration, i.e. when the Ruby source code is read and that happens only once.

There are two things to keep in mind here :

  • Rails follows lazy loading, i.e. it loads up the definition of a class when it requires it for the first time.
    • So when you hit url_a for the first time class A is loaded, i.e. its source is parsed
    • class B is not yet loaded. It gets loaded later when you hit url_b.
  • When a Ruby source file is parsed, all the code that is outside any function is executed right away. So the Base.result = method calls are executed when the two classes are loaded.

So the sequence of steps would be :

  • In the call to url_a, class A is parsed and Base.result = a sets @@var to a
  • Then in the call to url_b, class b is parsed and Base.result = b sets @@var to b and it remains so for all subsequent calls.

The following code snippet might help understand the second point :

irb(main):033:0> class ParseTest
irb(main):034:1> @@time_now = Time.now
irb(main):035:1> def speak_time
irb(main):036:2> puts @@time_now.to_s
irb(main):037:2> end
irb(main):038:1> end
=> nil
irb(main):039:0> pt = ParseTest.new
=> #<ParseTest:0x007f80758514c8>
irb(main):040:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):041:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):042:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):043:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):044:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):045:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):046:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):047:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):048:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):049:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):050:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):051:0> class ParseTest2 < ParseTest
irb(main):052:1> @@time_now = Time.now
irb(main):053:1> end
=> "2014-09-18T23:16:41.911+05:30"
irb(main):054:0> pt.speak_time
2014-09-18 23:16:41 +0530
=> nil
irb(main):055:0>

As you can see, after the ParseTest class definition was parsed once, the value of @@time_now did not change in any of the subsequent puts. The value of time was what it was when the source code was parsed.

However when I defined the ParseTest2 sub-class and its code was parsed, the same class variable was given a new value of time. This new value is reflected when I print it using the same old object of the base class.

This is what is happening in your code too.

Case 2 : Class level instance variable (i.e. @var in class definition outside any instance function)
Now, if instead of a class variable you use an instance variable in the class definition (i.e. outside any function), that is a very different. This case might appear a little confusing, so read and re-read the following code snippet if it appears confusing to you in the first go.

irb(main):089:0> class Base
irb(main):090:1> @time_now = Time.now
irb(main):091:1>
irb(main):092:1* def self.time_now=(time)
irb(main):093:2> @time_now = time
irb(main):094:2> end
irb(main):095:1>
irb(main):096:1* def self.time_now
irb(main):097:2> puts @time_now.to_s
irb(main):098:2> end
irb(main):099:1> end
=> nil
irb(main):100:0> class A < Base
irb(main):101:1> Base.time_now = Time.now
irb(main):102:1> end
=> "2014-09-18T23:33:26.514+05:30"
irb(main):103:0> Base.time_now
2014-09-18 23:33:26 +0530
=> nil
irb(main):104:0> A.time_now

=> nil
irb(main):105:0> A.time_now = Time.now
=> "2014-09-18T23:34:27.093+05:30"
irb(main):106:0> A.time_now
2014-09-18 23:34:27 +0530
=> nil
irb(main):107:0> Base.time_now
2014-09-18 23:33:26 +0530
=> nil
irb(main):108:0>

The class level instance variable is private to that class. It does not get passed on / shared during inheritance. So every class in the inheritance hierarchy has its own set of instance variables. However the methods do get passed on and those methods act on the instance variable of the class on which they were called. So depending on which class you call the time_now= setter method, the corresponding instance variable is set.

In your case you are always referring to the instance variable of the Base class. So the same set of steps happen as described in the previous case

  • In the call to url_a, class A is parsed and Base.result = a sets @var of Base to a
  • Then in the call to url_b, class b is parsed and Base.result = b sets @var of Base to b and it remains so for all subsequent calls.


Related Topics



Leave a reply



Submit