Skip to content

MR09 Mutant Class

Dave Strus edited this page Jan 31, 2016 · 2 revisions

Creating the Mutant Class

In this unit, we'll do the following:

  • Extract the concept of a mutant into a separate class

We'll learn about the following concepts or tools:

  • Data hiding
  • attr_accessor
  • Enumerable#map
  • Symbol#to_proc

We often create classes for the important nouns in our apps. Mutant is the main noun we're dealing with here, and it seems prudent to extract it into its own class.

To start, we'll include attribute accessors for real name, mutant name, and power.

class Mutant
  attr_accessor :real_name, :mutant_name, :power
end

attr_accessor give us getter and setter methods for the instance variables we specify as symbols.

So attr_accessor :real_name is equivalent to this:

class Mutant
  def real_name=(real_name)
    @real_name = real_name
  end

  def real_name
    @real_name
  end
end

Direct access to the instance variables stays restricted the class itself, but attr_accessor gives us methods we can use to set and retrieve their values.

Even the class as simple as this, we can use an instance of Mutant in place of a hash to hold each mutant's data inside RosterApplication. We need to update collect_mutant_data and mutant_data to use Mutant instances.

class RosterApplication
  def start
    @mutants = []
    puts 'Hello, mutant collector!'
  end

  def collect_mutant_data
    mutant = Mutant.new
    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

At the moment, this doesn't provide any obvious benefits over simply using a hash. We can do more with our new class though.

How about building that descriptive string in mutant_data within Mutant itself?

class Mutant
  attr_accessor :real_name, :mutant_name, :power

  def description
    "#{mutant_name} (also known as #{real_name}) has an incredible power: #{power}."
  end
end

# ...

  def mutant_data
    output = ''
    @mutants.each do |mutant|
      output += "#{mutant.description}\n"
    end
    output
  end

Now mutant_data simply needs to get the description of each mutant. It's up to Mutant to provide the actual descriptions. In fact, our each is just performing an operation on each item in the array, and then collecting the results. We can use Enumerable#map to collect each description in an array, and then return the result of joining the array with a newline as the separator.

def mutant_data
  descriptions = @mutants.map { |mutant| mutant.description }
  descriptions.join("\n")
end

@mutants.map { |mutant| mutant.description } will collect all the descriptions in an array, which we then assign to a local variable, descriptions.

descriptions.join("\n") combines those descriptions in a single string, with newlines in between each one. I'm liking this much better.

We can actually make this even more succinct. Since all our block is doing is calling a single method on each item, we can use Symbol#to_proc:

descriptions = @mutants.map(&:description)

How exactly Symbol#to_proc works is fairly complex. Suffice to say, if the items in @mutants all respond to description, we can throw an ampersand in front of the symbol :description to create a Proc that behaves exactly like the block { |mutant| mutant.description }.

This is now so simple that I think we just chain join on the end and skip the local variable.

def mutant_data
  @mutants.map(&:description).join("\n")
end

mutant_data no longer seems like a good name for this method. It doesn't really return mutant data, does it? It returns mutant descriptions. Let's change the name accordingly.

def mutant_descriptions
  @mutants.map(&:description).join("\n")
end

Don't forget to change the call to that method at the bottom of the script.

puts app.mutant_descriptions

We can improve this some more, but now is a good time to commit if you haven't done so in a while.