Why should @@class_variables be avoided in Ruby?
Class variables are often maligned because of their sometimes confusing behavior regarding inheritance:
class Foo
@@foo = 42
def self.foo
@@foo
end
end
class Bar < Foo
@@foo = 23
end
Foo.foo #=> 23
Bar.foo #=> 23
If you use class instance variables instead, you get:class Foo
@foo = 42
def self.foo
@foo
end
end
class Bar < Foo
@foo = 23
end
Foo.foo #=> 42
Bar.foo #=> 23
This is often more useful. 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:
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.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).
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. Should I use class variables or class-instance variables for class static variables in Ruby?
I recently discovered ActiveSupport defines class_inheritable_accessor
, which does what the class-instance variables do with the advantage that objects are not shared across inheritance, and you can have a default value for the variable when subclassing.
class Foo
class_inheritable_accessor :x, :y
end
Foo.x = 1
class Bar < Foo
end
Bar.x #=> 1
Bar.x = 3
Bar.x #=> 3
Foo.x #=> 1
More info hereJust for completeness: of the two presented options, I prefer going with the class-instance variables, since is often the expected behavior.
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]
Related Topics
How to Avoid Circular Creation of Associated Models in Factory_Girl
Change Time Zone in Pure Ruby (Not Rails)
Clean Install Osx 10.9.1 Returns "Undefined Method 'Path2Class'" When Trying to Install Gems
Warning While Installing The Rails Plugin
Rvm Can No Longer Install 1.8.7-P352 on MAC Os X Mountain Lion
Recommended Way to Generate a Presigned Url to S3 Bucket in Ruby
Getting a "Connection Reset by Peer" Error When Hitting Google Contacts API
Why Am I Getting "Unable to Autoload Constant" with Rails and Grape
How to Handle Serialized Edit Fields in an Active Admin Resource
How to Mount a Sinatra Application Inside Another Sinatra App
Ruby: Class C Includes Module M; Including Module N in M Does Not Affect C. What Gives
Filtering Sensitive Data with Vcr
Fixing The "Ruby Installation Is Missing Psych" Error
After_Save Callback to Set The Updated_By Column to The Current_User
Functional Testing of a Restful Post in Ruby on Rails
Nomethoderror: Undefined Method 'On' for Main:Object
How to (Massively) Reduce The Number of SQL Queries in Rails App