A Spooky Ruby Class

In Ruby, pretty much everything is an object. The number 5 is an object. The string “boo!” is an object. When we want to be able to create a lot of similar objects, we use classes. Classes are objects too.

A class is like a blueprint for something we call instances. Each object you create using a class is called an instance. Ruby has a lot of built-in classes. Let’s take a quick look at the String class. To create a new instance of any class, we call the new method. So to create instances of the String class we could type:

greeting = String.new("Hi!")
#=>"Hi!"
formal_greeting = String.new("Hello!")
#=>"Hello!"

These String instances are each their own objects but they also share a lot of methods that they get from the String class.

greeting.size
#=>3
formal_greeting.size
#=>6

The built-in classes are great and all, but let’s try making our own. Let’s make a class for spooky monsters.

class Monster
  def initialize(spookiness, spooky_grabber)
    @spookiness = spookiness
    @spooky_grabber = spooky_grabber
  end
end

A lot of stuff is actually going on here so let’s break it down a bit.

class Monster

The class keyword declares the beginning of a class. Our class can be referenced later by its class name, Monster.

``ruby def initialize(spookiness, spooky_grabber)


Here we declare the `initialize` method. `initialize` is a special method. Whenever a new instance is of a class created, that class's `initialize` method is automatically called on that instance. It also receives all the arguments that were passed to the new method. This is the place to set up your object.

```ruby
    @spookiness = spookiness
    @spooky_grabber = spooky_grabber

Just assigning the parameters to some variables. But what are those @ signs? Those @ signs indicate that these are instance variables. Each instance keeps track of it’s own instance variables. Each monster will have it’s own individual spookiness and spooky grabber.

Now we can create some instances!

ghost = Monster.new(1, "a chilling presence")
skeleton = Monster.new(2, "a bony claw")
fishman = Monster.new(2, "a damp webbed hand")
cthulu = Monster.new(3, "several eldritch tentacles")

The good news is we have our instances. The bad news is, they don’t really do much yet. Let’s add some methods to our class so we can at least inspect the values we set up.

class Monster
  def initialize(spookiness, spooky_grabber)
    @spookiness = spookiness
    @spooky_grabber = spooky_grabber
  end

  def spookiness
    @spookiness
  end

  def spooky_grabber
    @spooky_grabber
  end
end

We can use these methods to see the values of our instance variables.

ghost.spookiness
#=>1

That seems like a lot of code to write just to read each variable. I know what you’re thinking. This is Ruby. There’s gotta be a shorter way. That’s where the attr_reader keyword come into play.

class Monster
  attr_reader "spookiness"
end

is the same as

class Monster
  def spookiness
    @spookiness
  end
end

Great let’s add it to our code.

class Monster
  attr_reader "spookiness", "spooky_grabber"

And let’s add some methods to print spooky text to the console.

class Monster
  attr_reader "spookiness", "spooky_grabber"

  def initialize(spookiness, spooky_grabber)
    @spookiness = spookiness
    @spooky_grabber = spooky_grabber
  end

  def spook
    print "You suddenly feel #{@spooky_grabber} brush your shoulder"
    puts  "." * spookiness
  end

  def scare
    print "You suddenly feel #{@spooky_grabber} wrap around your neck"
  puts  "!" * spookiness
  end
end

The spook and scare methods are called ‘instance methods’. That’s because each instance can use them. And when you call an instance method it has access to its caller’s instance variables.

ghost.spook
#  You suddenly feel a chilling presence brush your shoulder.
#=>nil
skeleton.scare
#  You suddenly feel a bony claw wrap around your neck!!
#=>nil

Sometimes you have a method that’s related to a class, but doesn’t really apply to individual instances. You can create ‘class methods’ by using the self keyword. Here’s an example. Pay attention to how self is used.

class Monster
  def self.merge (monster_a, monster_b)
    spookiness = monster_a.spookiness + monster_b.spookiness
    grabber = monster_a.spooky_grabber + " and " +  monster_b.spooky_grabber
    Monster.new(spookiness, grabber)
  end
end

You call class methods like this:

spook_team = Monster.merge(fishman, cthulu)

This method combines the properties of two objects then creates and returns a new instance. The created instance also has all the same methods as the other instances.

spook_team.scare
#  You suddenly feel a damp webbed hand and several eldritch tentacles wrap around your neck!!!!!
#=>nil

Great, nice and spooky!

And here’s the full code for our class.

class Monster
  attr_reader "spookiness", "spooky_grabber"

  def initialize(spookiness, spooky_grabber)
    @spookiness = spookiness
    @spooky_grabber = spooky_grabber
  end

  def spook
    print "You suddenly feel #{@spooky_grabber} brush your shoulder"
    puts  "." * spookiness
  end

  def scare
    print "You suddenly feel #{@spooky_grabber} wrap around your neck"
    puts  "!" * spookiness
  end

  def self.merge (monster_a, monster_b)
    spookiness = monster_a.spookiness + monster_b.spookiness
    grabber = monster_a.spooky_grabber + " and " +  monster_b.spooky_grabber
    Monster.new(spookiness, grabber)
  end
end