Trials and tribulations of class variables in Ruby.
August 25, 2007
I guess I missed the discussion earlier this year about Ruby and class variables. There were a lot of posts about the problems with using class variables incorrectly, many of which, called for the use of class instance variables instead.
I recently fell into this class variable trap described in the posts above. Here is some code simulating my problem.
irb(main):001:0> class Person
irb(main):002:1> @@count = 0
irb(main):003:1> def initialize(name)
irb(main):004:2> @name = name
irb(main):005:2> @@count += 1
irb(main):006:2> end
irb(main):007:1> end
irb(main):008:0>
irb(main):009:0* class Customer < Person
irb(main):010:1> @@count = 0
irb(main):011:1> def self.count
irb(main):012:2> puts @@count
irb(main):013:2> end
irb(main):014:1> end
irb(main):015:0>
irb(main):016:0* a = Person.new("Jim Bob")
irb(main):017:0> b = Person.new("Jason Yates")
irb(main):018:0> c = Customer.new("Albert Einstein")
irb(main):019:0>
irb(main):020:0* puts Customer.count
3
You would expect to get ‘1′ back from the ‘Customer.count’ method. It is the more obvious behavior or the one with the least surprise. The issue is, as you may have already noticed, class variables are shared with its subclasses. This behavior, speaking from personal experience here, can lead to very confusing and hard to diagnose bugs.
The problem above is easily solved by the use of class instance variables.
irb(main):001:0> class Person
irb(main):002:1> class << self; attr_accessor :count; end
irb(main):003:1> def initialize(name)
irb(main):004:2> @name = name
irb(main):005:2> self.class.count ||= 0
irb(main):006:2> self.class.count += 1
irb(main):007:2> end
irb(main):008:1> end
irb(main):009:0>
irb(main):010:0* class Customer < Person
irb(main):011:1> end
irb(main):012:0>
irb(main):013:0* a = Person.new("Jim Bob")
irb(main):014:0> b = Person.new("Jason Yates")
irb(main):015:0> c = Customer.new("Albert Einstein")
irb(main):016:0>
irb(main):017:0* puts Customer.count
1
As you can see this simulates the expected behavior.
This will all become mute fairly soon anyways. In Ruby 1.9, class variables are now read-only by its subclasses. With this even the first example should work correctly.