1

I've come across the following example from this tutorial:

class Song
  @@plays = 0

  def initialize(name, artist, duration)
    @name = name
    @artist = artist
    @duration = duration
    @plays = 0
  end

  def play
    @plays += 1
    @@plays += 1
    "This song: #@plays plays. Total #@@plays plays."
  end
end

s1 = Song.new("Song1", "Artist1", 234)    # test songs
s2 = Song.new("Song2", "Artist2", 345)   

puts s1.play
puts s2.play
puts s1.play
puts s1.play 

Is @@plays politely accessible only inside the class Song? This commentary brings up the point of not recommending the use of class variables. Is it b/c they are often not required in common-day use, and create a lot of debugging headaches when used?

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
stanigator
  • 10,768
  • 34
  • 94
  • 129

3 Answers3

21

Class variables are never really required. But the reason isn't that they're shared state. I mean, it's good to avoid shared state where you can, but that's not the real problem here.

The reason they're recommended against is, as shown in that article, they are really confusing. In particular, a class's class variables are shared by its subclasses and instances of its subclasses. For example:

class Parent
end

class Child1 < Parent
  @@class_var = "Child1's"
end

class Child2 < Parent
  @@class_var = "Child2's"
end

With this code, Child1 and its instances will all see a class variable named @@class_var with the value "Child1's" and Child2 and its instances will all see a class variable named @@class_var with the value "Child2's". But suppose later on we reopen Parent and write this:

class Parent
  @@class_var = "Parent's"
end

Now Parent and the instances it creates will all see a class variable named @@class_var with the value "Parent's". But that's not all. Now that the parent class has this variable, Child1 and Child2 suddenly share the variable, so all of the @@class_vars have the value "Parent's". And if you reassign the variable in Child1, it's still shared, so all of the classes get updated. How confusing!

Instead of class variables, you can just use instance variables of the class, like this:

class Parent
  @class_var = "Parent's"
  def self.class_var
    @class_var
  end
end

class Child1 < Parent
  @class_var = "Child1's"
end

class Child2 < Parent
  @class_var = "Child2's"
end

Now, Parent.class_var will return "Parent's", Child1.class_var will return "Child1's" and Child2.class_var will return "Child2's" — just like you expect.

Chuck
  • 234,037
  • 30
  • 302
  • 389
2

A class variable is a variable that is shared among all instances of a class. This means only one variable value exists for all objects instantiated from this class. This means that if one object instance changes the value of the variable, that new value will essentially change for all other object instances. Another way of thinking of thinking of class variables is as global variables within the context of a single class.

@@plays #is a class variable
@plays  #is an instance variable
$plays  #is a global variable accessed outside a class

So in your example you created a class variable @@plays to calculate the total number of songs played for all songs. Since it is a class variable, it cannot be accessed outside the class alone. If you wanted to access the total number of plays you can use a global variable. They start with a dollar sign $plays (in your case). I warn you, you should stay away from using global variables as they are problematic for numerous reasons. One thing you may consider is to create a method that pushes all song instances into an array. You can then sum all plays across all songs through iterators. Way more secure, way less prone to programmer error.

Edit: Here are why global variables are bad

Are global variables bad?

Community
  • 1
  • 1
ctilley79
  • 2,151
  • 3
  • 31
  • 64
-3

The @@ variable will a class variable. This is generally bad practice. In your code its redundant because @plays == @@plays (unless you set @@plays elsewhere in your code (bad practice))

Actually now that I look at it, they aren't really the same. @plays keeps a count of how many times an individual song has been played, and @@plays will keep a count of all songs. Still, its likely bad practice to use @@plays. Usually, you'd have a parent class like "Player" that is managing all the songs. There should be an instance variable called @total_plays in the "Player" class.

user1182000
  • 1,625
  • 14
  • 12
  • So @plays and @@plays in this case are exactly identical? I understand why it's bad practice from connecting the dots between what you're saying and the article. – stanigator May 15 '12 at 05:40
  • 4
    **This is wrong**. `@@` is a class variable, not a global variable. `@plays` and `@@plays` are **not** the same. You also don't elaborate at all *why* it may be a bad practice. – Andrew Marshall May 15 '12 at 05:48
  • I agree. look at my answer below – ctilley79 May 15 '12 at 05:53
  • You're right Andrew. I used the wrong word. Its a class variable, but still generally a bad idea. – user1182000 May 15 '12 at 05:54
  • The questioner wanted to know if he could access the variable outside the class. – ctilley79 May 15 '12 at 05:58
  • Downvoted @user1182000 you are absolutely wrong, there is nothing wrong with using @@ class variables and this is an assumption you have not based on fact. There are plenty of valid reasons to use @@ such as avoiding attr_accessor and it's self.class methodology on an API wrapper that needs to share variables across all instances. AKA @@api_endpoint or something to that effect. – Jordon Bedwell Nov 05 '12 at 23:08
  • @JordonBedwell Au contraire, there *are* reasons to try to avoid class variables. I have a hard time coming up with more than one or two reasons, and even then, it's usually not necessary. I'd be interested in hearing your reasoning--do you have a blog post or references to which I can refer? – Dave Newton Apr 02 '13 at 22:04