Why Should We Avoid Using Class Variables @@ in Rails

Why should we avoid using class variables @@ in rails?

Simply because they are not thread safe. Many rails=capable servers are multi-threaded. That means there may be multiple running instances of your application at any given time and any request by one of your users is going to be arbitrarily assigned to one of them. Class variables are not shared between processes so there is a possibility that your class variable will be different in a subsequent request.

Even if you deliberately manage to run your app in a single threaded server, there is no guarantee that your app won't be restarted between requests, losing your class variable.

If you want functionality similar to what class variables provide, I strongly recommend that you look into key-value stores such as Memcached or Redis.

How can I avoid using class variables in Ruby

@@cost behaves more like a constant (i.e. it won't change during runtime), so you should use one instead:

COST = 0.0946

@@kwh should be an instance variable, since it is used only within the instantiated object, so you could use @kwh instead:

@kwh = (watt / 1000) * hours

And daily_cost = @@kwh * @@cost will become:

daily_cost = @kwh * COST

That will avoid the use of class variables, but you could also eliminate @kwh altogether since you don't use it anywhere else.

So, instead of:

def watt_to_kwh(hours)
@kwh = (watt / 1000) * hours
end

You could just do:

def watt_to_kwh(hours)
(watt / 1000) * hours
end

And use it like this in cost_of_energy method:

def cost_of_energy
puts "How many hours do you use the #{self.name} daily?"
hours = gets.chomp.to_i
daily_cost = watt_to_kwh(hours) * COST
montly_cost = daily_cost * 30
puts "Dayly cost: #{daily_cost}€"
puts "montly_cost: #{montly_cost}€"
end

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.

How to avoid class and global variables

You misunderstand the term "class instance variable". It means "instance variable on a Class object", not "instance variable on an instance of some class".

  class Person
attr_accessor :memories # instance variable, not shared

class << self
attr_accessor :memories # class instance variable, shared between
# all instances of this class
end
end

Obviously, sometimes you do need to use class instance variables. Refrain from using class variables (@@memories) as they are shared between all classes in the hierarchy (the class and its children), which may lead to surprising behaviour.

In Rails, are class attributes of arbitrary classes reset on each new request?

In the development environment, classes are reloaded on each request, so you will see class variables reset on each request.

In the production environment, classes are not reloaded on each request, so generally the class variables should be preserved across requests within a given Ruby process.

Bear in mind that some Rails servers might actually have multiple processes, so you're not guaranteed that all users and all requests will be sharing class variables.

Other Rails servers might be multi-threaded, so you have to be especially careful about how you mutate shared state (such as class variables).

For these reasons, it might be a good idea to avoid using class variables in this way as @andrew21 mentioned.

Are @@variables in a Ruby on Rails controller specific to the users session, or are all users going to see the same value?

@@ComputedData is a class variable. All users are going to see the same data, so baaaad idea.

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.

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"


Related Topics



Leave a reply



Submit