When working on a ruby application that doesn't connect to a database you'll need to store some critical data in Class Variables. Class Variables are available to the entire Class as well as any Instance of the Class. That's pretty useful.
Instance Variables are only available to the Instance they were set in.
And local variables can only be accessed in the local environment where they were created (for example in a method).
Don't Overuse Class and Instance Variables
It can be an easy mistake just to make everything an Instance or Class variable. One might think, "I won't need this info outside this method, but what's the harm?"
Well, a lot. The data you need in one method might get changed by another. So you'll believe a variable is empty but it's gotten a value from somewhere else. You'll need to be extra careful with names of variables. And it just forces some unnecessary storage of information. Once that method is over it's better just to forget that variable. Let your code focus on something else.
Using a Class Variable when you meant to use an Instance Variable (or worse, a local variable) is exponentially worse. Now every instance is effecting your variable when you only need it in one place to do one job.
Good Use of Class Variables
So as I got further along in learning to code in Ruby I got into the habit of only using Class Variables to store all the Instances of an object. You know, like this.
@@all = []
def initialize
@@all << self
end
This way later on I can access that list by another object. Even then I have to ask myself, will I ever need that list? In a non-database program the answer is probably.
Once I have written that little bit of code Class Variables are out of my mind.
Houston, We Have a Problem
I was working on a CLI program that interfaced with a free NASA API to give a list of asteroids that would be flying by Earth on a given day.
You could then choose from that list and find out when that asteroid will return to Earth. Maybe you check what asteroids are flying by on the day your child was born and then you want to see when the biggest asteroid (or the one that came closest to Earth) will return. It's basically a celestial anniversary of a very special day.
Everything was working pretty well except when a user opted to search the same date twice. My program creates a new object for each asteroid passing Earth (I call that object Asteroid) and then also creates objects for each Pass itself (asteroids are orbiting so they can pass by again and again). When I searched a date twice I'd just create doubles for each Pass and Asteroid.
Initially I solved this problem pretty elegantly. I just checked my @@all variable for any passes on that day. I assumed that if I stored one pass for the day in question then I had already grabbed all the data for that date from NASA's API.
That assumption, as they often are, is wrong. You see if a user looked up an asteroid to find it's next visit they will create a single pass on a future date. What if the user decides to see what other asteroids pass by on that same date?
So for example: I get a list of all the asteroids from the day my fictional friend Zane was born. I see that asteroid 2011 QF3 (that rolls off the tongue) was the biggest one in the sky that day. I check and see that it's coming back on March 29, 2030. Fun! Now let's see what other asteroids are flying by on that day. Well my code pulls that API and finds 6 asteroids coming on that day. I create those 6 Asteroids and their 6 Passes. But one of those Asteroids (2011 QF3) I have already created. And I even created it's Pass for that date. Now my data is getting corrupted with duplicates.
I tried numerous ways to check my @@all variable for Pass and Asteroid to stop this from happening, but the only solution I could find was after I hit the API checking that data against other instances. That seemed a wasteful hit of the API when it might not be necessary.
Saved by the Bel
I talked to my instructor, Annabel, about this issue and my desire to not have to wait until after I checked the API before I checked for doubles. She looked at my code for a few moments, saw the issue and noted that my class all variable won't work so I needed to create...
Before she even finished that sentence I realized what I needed to do. Create a second class variable. In my fear of over-using class and instance variables I didn't even consider the solution.
Talking to Annabel reminded me that this is what class variables are for! I created a new class variables that just kept track of dates I had hit the API for. By checking "@@all_searches" before hitting my API I could determine if I had created all the Passes and Asteroids for that day already. If I had, I could skip the API step and just search for the objects from my class variable "@@all" .
The same solution solved my creating a second Pass object if I searched for an asteroid's second visit twice. I created "@@next_visitation" so I knew if I had already called NASA's API for an asteroid's next visit. When the user asks for the next time an asteroid visits I don't know the date (yet). I only have the Asteroid Object. I know the next pass is in the future but there might be passes stored in our "@@all" variable that are actually the third, fourth or any number of later visits that this asteroid will next make to Earth so I need to definitively know I have that pass before I can trust the data in my "@@all" class variable.
@@all = []
@@all_searches = []
@@next_visitation = []
def initialize (search_type, pass_hash)
#code here stores the data for the Pass - not relevant to this post.
@@all << self
@@all_searches << self.pass_date if search_type == "date_search"
@@next_visitation << self.asteroid if search_type == "next_visit"
end
Conclusion
Know when to use Class Variables and Instance Variables instead of just avoiding them. If you close off an option because of inherent misuse you might be closing yourself off to he solution to your problem.
Top comments (0)