First steps with Ruby classes

Lately, we’ve been spending a lot of time at Dev Bootcamp learning how to organize objects and model behavior with classes in Ruby. That’s a really generic description, so here’s an example of some of the fun you can have by creating your own classes.

Let’s imagine we want to build some kind of a dog simulator, like The Sims: Dog Edition. In this awesome new game, we can create countless dogs and watch them interact in a virtual world. But, all dogs are unique. Breeds can vary in their physical characteristics and behaviors. And while dogs of the same breed are often similar, there’s lots of variation within any given breed, too. How can we model this?

Let’s start off with a general Dog class:

class Dog
  # Part 1
  attr_reader :name, :breed, :stubbornness, :friendliness, :intelligence

  # Part 2
  def initialize(name)
    @name = name
    @legs = 4
    @breed = "unknown"
    @stubbornness = rand(1..10)
    @friendliness = rand(1..10)
    @intelligence = rand(1..10)
  end

  # Part 3
  def bark
    puts "Woof woof!"
  end

  # Part 4
  def profile
    puts "name: #{self.name}"
    puts "breed: #{self.breed}"
    puts "stubbornness: #{self.stubbornness}"
    puts "friendliness: #{self.friendliness}"
    puts "intelligence: #{self.intelligence}"
  end
end

There’s four important parts to the Dog class:

  • The attr_reader line creates getter methods for each of the variables listed. In other words, this allows us to retrieve the value of those variables from outside the object.
  • In initialize, a new Dog object requires that we give it a name, and characteristics for the Dog are automatically generated. By default, every new Dog will have four legs and are of breed “unknown”, and have random values (from 1 to 10) on stubbornness, friendliness, and intelligence.
  • The bark method gives a Dog object the ability to bark.
  • The profile method will print out several of a Dog’s characteristics.

We could create a new Dog object called fido, representing a loveable but generic dog named, well, Fido:

fido = Dog.new(name="Fido")
# => Dog:0x007f301299f2e8 @name="Fido", 
#    @legs=4, @breed="unknown", @stubbornness=1, 
#    @friendliness=7, @intelligence=1

fido.bark
# => Woof woof!

fido.profile
# => name: Fido
#    breed: unknown
#    stubbornness: 1
#    friendliness: 7
#    intelligence: 1

# because we used attr_reader :stubbornness, 
# we can do this:
fido.stubbornness
# => 1

So the Dog class lets us generate dogs for our dog simulator, and every new dog will have a name, some random characterstics, and a way to bark. Let’s make it a little more sophisticated and add an even more specialized kind of Dog class to represent the revered Boston Terrier breed.

Mia, a Boston Terrier Mia, a Boston Terrier, stares into your soul

Here’s a start to defining the BostonTerrier class:

 
# Part 1
class BostonTerrier < Dog

  # Part 2
  def initialize(name)
    super
    @breed = "Boston Terrier"
    @stubbornness = rand(7..10)
    @friendliness = rand(7..10) 
    @intelligence = rand(5..8) 
  end

  # Part 3
  def bark
    puts "Yip yip!"
  end

  # Part 4
  def snore
    puts "zzzzzzzz waaaaa...." * 3
  end
end   

The BostonTerrier class builds on our existing Dog class and adds a few new wrinkles:

  • When defining the class, BostonTerrier < Dog indicates that BostonTerrier is a subclass of Dog and will inherent all of its properties.
  • Boston Terriers tend to be very stubborn and very affectionate, so we want to reflect that somehow when new one are created. By definining initialize here and using super, we inherent the same initialization method as the parent (or super) class Dog and add a twist: the randomly generated characteristics for Boston Terriers come from more specific ranges. This ensures that new Bostons are high in friendliness and stubbornness. We don’t modify how intelligence is initialized, so that could still be anything from 1 to 10, as defined in the parent Dog class.
  • We redefine the bark method, because Bostons are definitely more yip yip than woof woof. The Dog class already has a bark method, and this new yip yip bark is only available to Bostons. Non-Bostons still have the original woof woof bark.
  • Finally, we add a special new ability just for Bostons: snore!

So let’s make a Boston Terrier named Mia and see her in action:

mia = BostonTerrier.new(name="Mia")
# => BostonTerrier:0x007f30130993f0 @name="Mia", 
#    @legs=4, @breed="Boston Terrier", @stubbornness=7, 
#    @friendliness=10, @intelligence=10

mia.bark
# => Yip yip!

mia.profile
# => name: Mia 
#    breed: Boston Terrier 
#    stubbornness: 7
#    friendliness: 10
#    intelligence: 10

mia.snore
# => zzzzzzzz waaaaa....zzzzzzzz waaaaa....zzzzzzzz waaaaa....

# Poor Fido can't snore:
fido.snore
# =>  NoMethodError: undefined method `snore' for #Dog:0x007f301299f2e8 ...

# and Fido still goes woof:
fido.bark
# => Woof woof!

We can keep going, too! Here’s a subclass for Shetland Sheepdogs, another friendly breed known for their smarts and knack for herding:

class ShetlandSheepdog < Dog

  def initialize(name)
    super
    @breed = "Shetland Sheepdog"
    @friendliness = rand(4..7) 
    @intelligence = rand(8..10)
  end

  def herd(target) 
    puts "#{self.name} circles around #{target.name}!"
  end
end

The ShetlandSheepdog class inherents all of the generic Dog properties and has a unique method, herd, which takes another object as a parameter. Now our dogs can interact! Let’s make a Shetland Sheepdog named Shellie, and have her try to herd Mia:

shellie = ShetlandSheepdog.new(name="Shellie")
# => ShetlandSheepdog:0x007f301303a990 @name="Shellie",
#    @legs=4, @breed="Shetland Sheepdog", @stubbornness=4,
#    @friendliness=6, @intelligence=10>

shellie.bark
# => Woof woof!

shellie.herd(mia)
# => Shellie circles around Mia!

mia.snore
# => zzzzzzzz waaaaa....zzzzzzzz waaaaa....zzzzzzzz waaaaa....

Ok, so there isn’t a whole lot of drama in our dog universe yet. But, you can probably imagine some ways we could improve the herd method, e.g., making the outcome dependent on the friendliness of two dogs involved. We could also add another layer to our hierarchy, between generic dogs and specific breeds, which would group different types of breeds (herding dogs vs. lap dogs) and could simplify our breed classes.

Just the little bit I’ve learned about classes and inheritance has been very powerful and has got me thinking about objects in a much more structured way. It’s pretty exciting stuff, but this is only the very beginning. If you’d like to carry on my canine version of the Sims, knock yourself out, but let me know when the checks start rolling in.