Class variables in Ruby - ruby

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?

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.

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?

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.

Related

Class Variables Overriding

I was playing around with class variables, and I knew that I could override a class variable in a subclass but I didn't realize where I call a class method matters. For example;
class Vehicle
##wheels = 4
def self.wheels
##wheels
end
end
puts Vehicle.wheels #4
puts Vehicle.wheels #4
puts Vehicle.wheels #4
class Motorcycle < Vehicle
##wheels = 2
end
The puts statement outputs 4, that all makes sense to me because I figured that you ##wheels is not overridden until an object is instantiated from the Motorcycle class. So then I did this:
class Vehicle
##wheels = 4
def self.wheels
##wheels
end
end
class Motorcycle < Vehicle
##wheels = 2
end
puts Vehicle.wheels #2
puts Vehicle.wheels #2
puts Vehicle.wheels #2
Now once I move those three lines to after the class Motorcycle they output 2... even though a Motorcycle object hasn't been instantiated. This confuses me. In detail what is happening here? Is it because class variables are loaded when we try to access it? How is this possible?
First of all, instantiation has nothing to do with it; this is a class variable, not an instance variable. Your code never instantiates anything; nor, as written, does it need to. We're just talking about some classes. But classes are first-class objects in Ruby, so that's fine.
Second, order does matter, because in Ruby all statements are executable. When you say
class Motorcycle < Vehicle
##wheels = 2
end
...all of that code executes right now, at the moment you say it — the moment when it is encountered as Ruby walks down the page obeying instructions. That code is not a mere template to be obeyed at some future time; it says: "create a Motorcycle class and set its class variable wheels to 2, now."
Third, you're not "overriding" anything. In the class Motorcycle, ##wheels is the very same class variable ##wheels that you defined for Vehicle earlier. It is not some other variable that "overrides" it. This is a single value that works like a kind of namespaced global, reachable from the class, any subclasses, and any instances thereof.
For more information, see (e.g.):
How do I override a variable in a Ruby subclass without affecting the superclass?
Ruby class instance variable vs. class variable
https://www.ruby-lang.org/en/documentation/faq/8/
If you want something you can override, what you're looking for is probably more like an instance variable in a class context, often called a "class instance variable" — e.g. like this:
class Vehicle
#wheels = 4
class << self
attr_reader :wheels
end
end
class Motorcycle < Vehicle
#wheels = 2
end
puts Vehicle.wheels # 4
puts Motorcycle.wheels # 2
All occurances of ##wheels refer to the exactly same variable. First you set it to 4, then you set it to 2. Why does it surprise you, that it has the value 2? Use #wheels instead (as matt) suggested, if you want to have a separate variable for both.
Having said this (and this is the only reason I'm writing this answer, because matt has already covered everything else in his answer), your usage of this "variable" suggests that is is supposed to be an invariable property of the respective class. That is, every motorcycle is supposed to have 2 wheels. In this case, it would perhaps make more sense to make it a constant of its class:
class Motorcycle < Vehicle
WHEELS = 2
end
class Unicycle < Vehicle
WHEELS = 1
end
and use it as
puts Motorcycle::WHEELS
If you have a variable v, where you only know that it is some subclasss of Vehicle, you can write
puts v.class::WHEELS
Whether you also define WHEELS in the class Vehicle itself, its a design question. If you intend to treat Vehicle as abstract class, it does not make sense to define a fixed number of wheels in it. For orthogonality, you could catch this case by doing a
class Vehicle
WHEELS = nil
end
if you want to, or just don't define it and rely on the fact that your code will raise an exception, if you try to access the wheels of something which is "just a plain vehicle".
If you do want to instantiate a Vehicle, of course it may make sense to set the constants there to, i.e., 4.
However, in this case I would consider allowing individual instances having a different number of wheels. The Reliant Robin was a car with 3 wheels, and trucks have sometimes 6 or 8 wheels. For maximum generality, I would therefore make the number of wheels an instance variable, and of course you can provide a default value:
class Vehicle
attr_reader :wheels
def initialize(wheels=4)
#wheels=wheels
end
end
class Motorcycle < Vehicle
attr_reader :wheels
def initialize(wheels=2)
super(wheels)
end
end
m = Motorcycle.new
puts m.wheels

Is it possible to swap out nearly identical classes in Ruby?

class Parent
def punish!
end
end
class Mom < Parent
end
class Dad < Parent
end
If I have an instance of Dad but want to make it an instance of Mom, is this possible in Ruby?
If by "swap" you mean substitute in the context of a variable:
parent = Mom.new
parent.punish!
parent = Dad.new
parent.punish!
You can reassign a variable at any time. What you can't do is pervert an instance of object from one class into another. Once created it's basically stuck in that class.
The same principle here applies to object attributes and other places where an object reference might be saved.

Ruby: When defining a class is "initialize" for convenience?

I'm new to programming and am trying to figure out the purpose of "initialize" in creating a class.
Here's an example:
class Person
def initialize(name)
#name = name
#pet = nil
#home = 'NYC'
end
end
So initializing is to create a bunch of attributes that I can pull out directly by saying Person.name and Person.pet and Person.home right? Is "initialize" just to compact a bunch of variables into one place? Would I accomplish the same thing doing this:
class Person
pet = nil
home = 'NYC'
#not so sure how to replicate the #name here.
end
Wouldn't I be able to access the values with Person.pet and Person.home the same way as I would in the first code?
This is a little tricky in Ruby (as opposed to, say, Java) since both classes and instances of classes are actual objects at runtime. As such, a class has its own set of variables, and each instance of that class also gets its own set of variables (distinct from the class's variables).
When you say
class Person
pet = nil
end
You're setting a variable, pet, which is local only to the class object called Person.
The way to manipulate the variables of an instance of a class is to use the variables in methods:
class Person
def initialize
pet = nil
end
end
Here, pet refers to a local variable of a given instance of Person. Of course, this pet variable is pretty useless as defined, since it's just a local variable that goes away after the initialize function completes. The way to make this variable persist for the lifetime of the instance is to make it an instance variable, which you accomplish by prefixing it with a #. And thus we arrive at your first initialize:
class Person
def initialize
#pet = nil
# And so on
end
end
So, as to why you need initialize. Since the only way to set the instance variables of instances of Person is within methods of Person, this initialization needs to be in some method. initialize is just the convenient name for a method which is automatically called when your instance is first created.
Initialize is a method usually referred as an object constructor. It is used when you call Person.new("Bob") and it will give you an instance of that Person object. The # symbol you see before the variables in the initialize makes the variable an instance variable meaning that variable will only be accessed once you have an instance of that object and it will stay there for the lifetime of that instance.
For example
person = Person.new("Bob")
person.name #Will output Bob
person.home #Will output NYC
Classes are objects and doing this:
class Person
pet = nil
home = 'NYC'
end
is just creating local variables pet and home and will be outside of the scope of the class. This means calling Person.pet and Person.home will just give you an error. I would suggest do a little reading on Object Oriented Programming (OOP) and if you have any more questions throw them in stackoverflow :D

Class Variables

Explain please, I can not understand.
class Foo
#a = 123
##b = 123
end
What are the advantages of variable objects-classes and the class variables? When should I use first, and in which the second?
Instance level variables area created anew for each instance of the class. For example, a variable #id should probably be unique for each instance of Foo. However, there may be some values that should be the same for every instance of the type. In that case, a class variable would be more appropriate.
One important side effect of class level variables is that they are shared amongst derived classes as well. This means that changing the value in a subclass of 'Foo' will change it for 'Foo' objects as well. This may be what you want, but it can be a bit surprising to find out the hard way.
For example:
class Foo
##some_var = 1
def bar
puts(##some_var)
end
end
class Baz < Foo
def perhaps_unexpected
##some_var = 10
Foo.new.bar #prints '10'
end
end
Use a class variable when you want all the instances of the class to share that variable, and use an instance variable when you want each instance to have its own non-shared variable.

How to access instance variables from one class while inside another class

I'm really new to Ruby. And by new - less than 16 hours, but my boss gave me some Ruby code to add to. However, I found it was one giant file and not modular at all, so I decided to clean it up. Now that I've broken it up into several files/classes (generally speaking, 1 class per file,) I'm having problems piecing it together for it to work again. Originally everything was part of the same class, so the calls worked, but it looked ugly and it took an entire work day just to figure it out. I want to avoid that for the future as this code will grow much larger before it is done.
My main issue looks like the following (simplified, obviously):
class TestDevice
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(identifier, level, section, message)
...
#loghash[identifier] = { level => { section => message }}
...
end
end
device = TestDevice.new
After that, it calls out to other class methods, and those class methods reference back to the class Log for their logging needs. Of course, Log needs to access "device.loghash" somehow to log the information in that hash. But I can't figure out how to make that happen outside of passing the contents of "loghash" to every method, so that they, in turn, can pass it, and then return the value back to the origination point and then logging it at the end, but that seems really clumsy and awkward.
I'm hoping I am really just missing something.
To create accessors for instance variables the simple way, use attr_accessor.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
You can also manually define an accessor.
class TestDevice
def loghash
#loghash
end
def loghash=(val)
#loghash = val
end
end
This is effectively what attr_accessor does behind the scenes.
how about passing the device object as a parameter to the msg function? (I'm assuming that there can be many devices in your program, otherwise you can use singleton pattern).
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(device, identifier, level, section, message)
...
device.loghash[identifier] = { level => { section => message }}
...
end
end
So you need to learn the rules of ruby scoping.
Ruby variables have different scope, depending on their prefix:
$global_variables start with a $, and are available to everyone.
#instance_variables start with a single #, and are stored with the current value of self. If two
scopes share the same value of self (they're both instance methods, for example),
then both share the same instance variables
##class_variable start with ##, and are stored with the class. They're
shared between all instances of a class - and all instances of subclasses
of that class.
Constants start with a capital letter, and may be all caps. Like class
variables, they're stored with the current self.class, but they also
trickle up the hierarchy - so if you have a class defined in a module,
the instances of the class can access the module's constants as well.
Constants defined outside of a class have global scope.
Note that a constant variable means that which object is bound to the constant
won't change, not that the object itself won't change internal state.
local_variables start with a lowercase letter
You can read more about scope here.
Local variables scoping rules are mainly standard - they're available in
all subscopes of the one in which they are defined except when we move into
a module, class, or method definition. So if we look at your code from your
answer
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
device = TestDevice.new
class Somethingelse
def self.something
device.loghash='something here' # doesn't work
end
end
The scope of the device local variable defined at the toplevel does not include the Somethingelse.something
method definition. So the device local variable used in the Somethingelse.something method definition is a different (empty) variable. If you want the scoping to work that way, you should use a constant or a global variable.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
DEVICE = TestDevice.new
$has_logged = false
class Somethingelse
def self.something
DEVICE.loghash='something here'
$has_logged = true
end
end
p DEVICE.loghash # prints `{}`
p $has_logged # prints `false`
Somethingelse.something
p DEVICE.loghash # prints `"something here"`
p $has_logged # prints `true`

Resources