-
Notifications
You must be signed in to change notification settings - Fork 0
MR08 Using Instance Variables
In this unit, we'll do the following:
- Improve our class's organization
- Store data for multiple mutants
We'll learn about the following concepts or tools:
- Instance variables
- Arrays
- Hashes
Enumerable#each
The last line of collect_mutant_data
prints back the data we've already collected. That's really a completely different job from collecting the data. Our code will be much easier to read, test, debug, and maintain if every method has only one job. Let's extract that last line into a new method.
We could create a method called print_mutant_data
.
def print_mutant_data
puts "#{mutant_name} (also known as #{real_name}) has an incredible power: #{power}."
end
It might be nicer if the method simply returned that string, and it were up to the caller to decide whether to print it to the screen or do something else with it. In that case, mutant_data
would be a better name for it.
def mutant_data
"#{mutant_name} (also known as #{real_name}) has an incredible power: #{power}."
end
Then we can call it and print the result after each call to collect_mutant_data
.
3.times do
app.collect_mutant_data
puts app.mutant_data
end
This won't quite work, will it? We assigned the data to local variables in collect_mutant_data
, so they won't be available to mutant_data
. Since we want these variables to retain their values as long as our instance sticks around, we should use instance variables, rather than local variables.
Instance variables remain in scope for the life of the instance. Their names must start with the
@
character. It is conventional to follow the naming rules for local variables beyond that.
By changing these to instance variables, we can access them from both methods. The variables still accessible only within the class itself. We can't access instance variables from the outside (e.g. app.mutant_name
).
class RosterApplication
def start
puts 'Hello, mutant collector!'
end
def collect_mutant_data
print 'Real name: '
@real_name = gets.chomp
print 'Mutant name: '
@mutant_name = gets.chomp
print 'Power: '
@power = gets.chomp
end
def mutant_data
"#{@mutant_name} (also known as #{@real_name}) has an incredible power: #{@power}."
end
end
This feels like an improvement. Our class still only holds the data for one mutant at a time though. Each call to collect_mutant_data
overwrites the data that's already there. It seems like we need an array to store data for multiple mutants at once.
What kind of objects should the array hold? It would be pretty messy to stick the individual strings in the array, because we wouldn't have a good way to tell where one mutant's data ends and another's begins. It would be equally tricky to tell a real name from a mutant name.
Let's store the data for each individual mutant in a hash, and store the hashes in an array. Only the array would need to be an instance variable, as it would contain all of the data that we need to persist. The hash containing an individual mutant's could be a local variable until we push it onto the array.
Before we attempt to push something onto the array, we need to be sure that the array exists. Let's an empty array to an instance variable when the application starts.
def start
@mutants = []
puts 'Hello, mutant collector!'
end
Now we can update collect_mutant_data
to push a hash onto the array.
def collect_mutant_data
mutant = {}
print 'Real name: '
mutant[:real_name] = gets.chomp
print 'Mutant name: '
mutant[:mutant_name] = gets.chomp
print 'Power: '
mutant[:power] = gets.chomp
@mutants << mutant
end
Similarly, we need to update mutant_data
to retrieve the data from the array. In fact, now that we can store data for multiple mutants, mutant_data
should probably retrieve the data for all of the mutants that have been added, rather than just the most recent.
We'll need to loop over the array, and build the string that we'll ultimately return.
If we define the string inside the loop, it will be local to the loop, and we won't have access to it at the end. For that reason, let's be sure to initialize it before we enter the loop.
def mutant_data
output = ''
@mutants.each do |mutant|
output += "#{mutant[:mutant_name]} (also known as #{mutant[:real_name]}) has an incredible power: #{mutant[:power]}."
end
output
end
Notice that I'm naming the block's parameter mutant
. That's the name that will be used for each individual item in the array within the block. We could use a more generic name, like item
, but it's good to give it a name that tells us what each item in the array is. Each item is a mutant, so we'll name it mutant
.
Each time through, I'm simply concatenating a string for that individual mutant onto output
.
If you run it like this, you'll notice the sentences are all smashed together, with not even a space between each one. Let's put a newline at the end of each.
def mutant_data
output = ''
@mutants.each do |mutant|
output += "#{mutant[:mutant_name]} (also known as #{mutant[:real_name]}) has an incredible power: #{mutant[:power]}.\n"
end
output
end
Since mutant_data
now returns data about every mutant, we only need to call it once, after we've collected all the data.
app = RosterApplication.new
app.start
3.times do
app.collect_mutant_data
end
puts app.mutant_data
All told, our script now looks like this:
#!/usr/bin/env ruby
require 'pry'
class RosterApplication
def start
@mutants = []
puts 'Hello, mutant collector!'
end
def collect_mutant_data
mutant = {}
print 'Real name: '
mutant[:real_name] = gets.chomp
print 'Mutant name: '
mutant[:mutant_name] = gets.chomp
print 'Power: '
mutant[:power] = gets.chomp
@mutants << mutant
end
def mutant_data
output = ''
@mutants.each do |mutant|
output += "#{mutant[:mutant_name]} (also known as #{mutant[:real_name]}) has an incredible power: #{mutant[:power]}.\n"
end
output
end
end
app = RosterApplication.new
app.start
3.times do
app.collect_mutant_data
end
puts app.mutant_data
Our code is looking better, but there's a fairly obvious candidate for a new class.