Ruby wizardry, p.16

Ruby Wizardry, page 16

 

Ruby Wizardry
Select Voice:
Brian (uk)
Emma (uk)  
Amy (uk)
Eric (us)
Ivy (us)
Joey (us)
Salli (us)  
Justin (us)
Jennifer (us)  
Kimberly (us)  
Kendra (us)
Russell (au)
Nicole (au)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

Larger Font   Reset Font Size   Smaller Font  

  Overriding Methods: Pirates are People, Too

  “Now that, we can do,” said the Queen. “Any subclass can override a method it inherits from its superclass at any time. Let’s have a look. We’ll create a superclass called Person and subclass called Pirate, with a speak method for both. Of course, pirates and regular people speak pretty differently, don’t they?” Scarlet and Ruben nodded. “So,” the Queen continued, “the two speak methods will be different.” She typed into the Computing Contraption:

  pirates_and_people.rb

  ➊ class Person attr_reader :name def initialize(name) @name = name end def speak puts 'Hello!' end end ➋ class Pirate < Person def speak puts 'Arr!' end end

  “Starting at ➊, I’ve defined the Person class with an attr_reader :name, so we’ll be able to get and change the name of any Person instances,” the Queen said. “The initialize method sets the name to the string we’ll pass in when we call Person.new, and the speak method just prints out 'Hello!'”

  “With you so far!” said the King.

  “Next, at ➋, I’ve defined the Pirate class to inherit from Person, so Pirate instances will be able to do anything a Person instance can do,” said the Queen. “But! I’ve given Pirate its very own speak method that prints 'Arr!'. We’ll see how that works in a moment. First, let’s go ahead and create an instance of each class to make sure we can create it and get its name without any trouble.”

  >> load 'pirates_and_people.rb' => true >> esmeralda = Person.new('Esmeralda') => # >> rubybeard = Pirate.new('RubyBeard') => # >> esmeralda.name => "Esmeralda" >> rubybeard.name => "RubyBeard"

  “Now, let’s test our speak method,” the Queen said. “Because Pirate created its own speak method, instances of Pirate will use that one instead of the one inherited from Person,” she explained. “But since we didn’t change the name and name= methods given to Pirate by attr_reader :name, which it inherited, we can get and change names the same way for both people and pirates!”

  >> esmeralda.speak Hello! => nil >> rubybeard.speak Arr! => nil

  “That’s really cool,” Ruben said, “but when would we decide to override a method?”

  “Any time one class inherits from another and you want most of the same behavior, but not all,” said the Queen. “In this case, we want a Pirate to be a Person and to be created like one, but we want to make sure our Pirates sound like pirates. So we simply override the methods we want to be different between regular old people and pirates!”

  “That makes sense,” said Scarlet, “but what if we want a little of both? That is, what if we want to modify a method we inherit, but not completely replace it?”

  Using super

  “I’m so glad you asked,” said the Queen. “That’s absolutely something Ruby lets us do—all we need is the super keyword. Using the Animal class we created earlier, we’ll create a new version of a method that already exists, just like we did with the speak method, and add our new code. Then, we’ll use super to tell Ruby: ‘Okay, I’m done adding new things to this method! Now have it do all the things the superclass’s version of the method does.’ It works like this,” she said, and typed:

  super_dog.rb

  class Dog < Animal def initialize(name) puts 'Just made a new dog!' super end end

  “Now we can create a Dog class that inherits from Animal, just like before,” said the Queen.

  >> load 'super_dog.rb’ => true >> dog = Dog.new('Bigelow') Just made a new dog! => #

  “Here, though, we’ve given Dog its own initialize method, which Dog instances will use instead of the one inherited from Animal,” the Queen continued.

  “Just like pirates used their own speak method instead of the one from Person,” Ruben said.

  “Exactly!” said the Queen. “We added our own puts statement to the Dog initialize method to print out a message, but then we used super to tell Ruby: ‘Okay! Now, use Animal’s initialize method.’ All super does is call the version of the method from the superclass! Since Animal’s initialize method sets the @name and the @legs instance variables for us, you see not only @name="Bigelow" but @legs=4!”

  “Gracious me, that’s astounding,” said the King, who had finally dried himself off. “Is there anything Ruby can’t do?”

  “That’s nothing,” said the Queen. “Now the real fun begins. We’ll use inheritance, method overriding, and super to create some trusty friends to defend us against the intruders in our kingdom!”

  Protecting the Kingdom with GuardDogs and FlyingMonkeys

  “But before we do that,” said the Queen, “let’s get back to our Dogs and Monkeys. First, I’ll redefine a Dog class, since it’s been a bit since we looked at it.” She typed into her Computing Contraption:

  guard_dog.rb

  class Dog < Animal attr_accessor :name def initialize(name) @name = name end def bark puts 'Arf!' end end

  “Our Dog class inherits from Animal and will be initialized with a name, and it will have a bark method to let it bark whenever it likes,” said the Queen. “Next, I’ll create a brand-new class that inherits from Dog. Let’s keep adding to guard_dog.rb!” She typed into the Computing Contraption:

  guard_dog.rb

  class GuardDog < Dog ➊ attr_accessor :strength ➋ def initialize(name, strength) @strength = strength super(name) end ➌ def bark puts 'Stop, in the name of the law!' end ➍ def attack puts "Did #{rand(strength)} damage!" end end

  “Here, I’ve created a GuardDog class that inherits from Dog. At ➊, we have an attr_accessor for :strength, so we’ll be able to set and get the strength of our new guard dog. Next, I added an initialize method at ➋ that partly overrides the one from Dog: it sets the GuardDog’s @strength, then calls super with just the name to use Dog’s initialize method, which sets the @name. At ➌, I completely overrode the bark method from Dog and gave GuardDog its own phrase to say.

  Finally at ➍, I added a brand-new attack method that prints out a string saying how much damage the dog did. That method uses Ruby’s built-in rand method to choose a random number between zero and whatever the GuardDog’s strength is.”

  “Wow!” said Ruben. “That’s amazing! And I didn’t know you could call super with arguments.”

  “Oh, yes,” said the Queen. “If you call super by itself, it calls the superclass’s initialize method with all of the arguments the subclass’s initialize method got. GuardDog takes one more argument than Dog—it takes strength as well as name—and that would cause an error if we tried to give both of those to Dog, which is created only with a name. So we call super with just name to make sure that Dog’s initialize method gets the number of arguments it expects.”

  Every GuardDog Has His Day

  “Now then,” the Queen continued, “let’s create a new GuardDog and test it out!”

  >> load('guard_dog.rb') => true >> rex = GuardDog.new('Rex', 7) => # >> rex.strength => 7 >> rex.bark Stop, in the name of the law! => nil >> rex.attack Did 1 damage! => nil >> rex.attack Did 4 damage! => nil

  “Now we’ve got a special kind of dog—a GuardDog—with its own set of methods!” The Queen said. “We partly overrode its initialize method because we wanted it to have strength, but then we used super to finish creating it like a regular dog. We overrode bark because we wanted our GuardDog to have the bark method, then finished up by adding a completely new attack method that GuardDogs have but Dogs don’t.”

  “I’m starting to get it now,” said Ruben. “We use inheritance to minimize the amount of code we have to retype, and we override methods when we want to make exceptions and give our subclasses special behavior!” The Queen nodded.

  “Don’t forget super,” Scarlet said. “We use that when we want to partly change the behavior of a method in a subclass, but not completely replace it.”

  The King furrowed his brow. “This makes sense, but could we see a bit more?” he asked. “It’s an awful lot to keep in my head all at once.”

  Once More, with Feeling!

  Ruben nodded. “Could we have one more example, just to be sure we understand?” he asked.

  “Of course,” said the Queen. “Here’s another example of inheritance, method overriding, and super, this time using our trusty Monkey class. Let’s make Monkey look like this,” she said, and typed:

  flying_monkey.rb

  class Monkey < Animal attr_reader :name, :arms def initialize(name, arms = 2) @name = name @arms = arms end def make_sounds puts 'Eeh ooh ooh!' end end

  “Here, we have a Monkey class. Using attr_reader, we can get (but not change) our monkey’s name and number of arms, which defaults to 2. We also have a make_sounds method that prints out a string.”

  “Looks pretty standard,” said the King.

  “Next,” the Queen continued, “we’ll create a FlyingMonkey class that inherits from Monkey. We’ll keep adding to flying_monkey.rb!” She typed into her Computing Contraption:

  flying_monkey.rb

  ➊ class FlyingMonkey < Monkey ➋ attr_reader :wings ➌ def initialize(name, wings, arms = 2) @wings = wings super(name, arms) end ➍ def throw_coconuts coconuts = rand(arms) damage = coconuts * wings puts "Threw #{coconuts} coconuts! It did #{damage} damage." end end

  “For our FlyingMonkey class,” said the Queen, “we first inherit from Monkey ➊. Next, we add an attr_reader for :wings so we know how many wings our FlyingMonkey has ➋. We initialize the flying monkey with a certain number of @wings, but then call super to have the Monkey class take care of setting the @name and number of @arms ➌. We then define a brand-new throw_coconuts method ➍ that uses Ruby’s built-in rand method to calculate how much damage the flying monkey can do by throwing coconuts. The number of coconuts is a random number between zero and the flying monkey’s number of arms, and the damage is that number multiplied by the number of wings the monkey has, because monkeys with more wings can fly higher.”

  “Okay!” said the Queen. “Let’s create a flying monkey and test out his methods.”

  >> load 'flying_monkey.rb' => true >> oswald = FlyingMonkey.new('Oswald', 6, 4) => # >> oswald.make_sounds Eeh ooh ooh! => nil >> oswald.throw_coconuts Threw 3 coconuts! It did 18 damage. => nil

  “Amazing!” said Scarlet. “We create the FlyingMonkey by using its own initialize method for wings, then letting Monkey finish up by setting the name and number of arms. And because FlyingMonkey inherits from Monkey, a flying monkey can not only throw_coconuts but can also use Monkey’s make_sounds method!”

  “Huzzah!” said the King. “I’ll bet that monkey is excellent at throwing coconuts, too. Which I suppose is only natural for a flying monkey.”

  “And I’ll bet he makes very good monkey sounds,” added Ruben.

  “I guess that makes sense,” said Scarlet, “but something’s been bothering me: why does our GuardDog know how to talk?”

  “He’s a very smart dog,” said the Queen.

  “Very,” said the King.

  “Speaking of,” said the Queen, “I think it’s high time we put our guard dogs and flying monkeys to work!” She pressed a button on the arm of her chair, and her Computing Contraption began to hum. In a matter of seconds, doors slid open on all sides of her office, and dozens of guard dogs and flying monkeys emerged!

  “Taco Tuesdays!” said the King. “And I thought all these gadgets and hacking conferences were a waste of time and money.”

  “On the contrary,” said the Queen. “I think they just might save the kingdom!”

  The King opened his mouth to speak, but at that very moment, a bright red telephone began ringing madly on the Queen’s desk. She picked it up. “Hello?” she said. She waited a moment, and then her eyes went wide. “Stay right where you are! We’re on our way!” She hung up and jumped from her chair. “The guards have news!” she said. “They’re down in the Royal Stables. Quickly now, let’s go!”

  And with that, the four of them dashed from the Queen’s office and headed for the stables out back, the guard dogs charging ahead and the flying monkeys following close behind.

  The Queen’s Machine

  This is getting exciting! While the King, the Queen, Ruben, and Scarlet go catch the bad guys, let’s jump in and help the Queen create a Ruby class to help keep all her royal business secret; after all, there’s only so much GuardDogs and FlyingMonkeys can do! I’m thinking some kind of login account for her Computing Contraption that’s a bit more secure than what she’s been using so far might be just what we need; we don’t want anyone breaking in again anytime soon. So, we’ll set up an Account class with a password for the Queen to use to log in to her computer.

  Let’s begin by making a new file called secrecy.rb and typing the following code.

  secrecy.rb

  ➊ class Account attr_accessor :username, :password ➋ def initialize(username, password) @username = username @password = password end end ➌ class SuperSecretAccount < Account ➍ def initialize(username, password) @reset_attempts = 0 super(username, password) end ➎ def password=(new_password) while @reset_attempts < 3 print 'Current password?: ' current_password = gets.chomp if @password == current_password @password = new_password puts "Password changed to: #{new_password}" break else @reset_attempts += 1 puts "That's not the right password." puts "Attempt #{@reset_attempts} of 3 used up!" end end end ➏ def password 'The password is secret!' end end ➐ regular = Account.new('Your name', 'your password') super_safe = SuperSecretAccount.new('Your name', 'your password') ➑ regular = Account.new('Your name', 'your password') super_safe = SuperSecretAccount.new('Your name', 'your password') puts "Your regular account password is: #{regular.password}" regular.password = 'Something else!' puts "Your regular account password is now: #{regular.password}" puts "If we try to see the secret account password, we get: #{super_ safe.password}" changed_password = 'Something else!' puts "Trying to change your secret account password to: #{changed_ password}..." super_safe.password = changed_password

  This is a long one, so let’s go through it step-by-step.

  First, we create a basic Account class at ➊ that sets up some instance variables (check them out in the initialize method at ➋). Instances of the Account class can have their @username and @password read and changed by any Ruby code that happens to want to, thanks to the attr_accessor for both :username and :password.

  We’re off to a pretty good start! This code lets us create an account for someone and lets that person set her password, just as you might do for a website or your email. The problem, though, is that this code lets any Ruby code change the user’s password, which we definitely don’t want.

  To fix that, we create our SuperSecretAccount class at ➌ that inherits from Account, and here’s where things get interesting. First, SuperSecretAccount’s initialize method also takes a username and password, and it passes these to super to let Account take care of setting those instance variables ➍. The SuperSecretAccount also creates a new instance variable, @reset_attempts, to keep track of how many times a user tries to log in.

  Next, the SuperSecretAccount class overrides the password= method ➎ (one of the two created by Account’s attr_accessor :password), so it requires a user to enter her old password in order to change it. If she enters the correct password, the program updates the password and immediately breaks out of the while loop; if she tries unsuccessfully three times, the program exits without changing the password.

  After that, the SuperSecretAccount class overrides the password method at ➏ (the other one created by Account’s attr_accessor :password) and makes it print the string The password is secret! instead of giving up the password as it normally would. Finally, we create a couple of accounts ➐ and try getting and setting the passwords ➑.

  You can run the code in your file by typing ruby secrecy.rb from the command line. Make sure you’re in the same folder as your secrecy.rb file and type:

  $ ruby secrecy.rb

  Here’s the output I get (yours might be a little different, depending on what you enter when you run the script):

  ➊ Your regular account password is: your password Your regular account password is now: Something else! ➋ If we try to see the secret account password, we get: The password is secret! ➌ Trying to change your secret account password to Something else!... Current password?: lasers That's not the right password. Attempt 1 of 3 used up! Current password?: ninjas That's not the right password. Attempt 2 of 3 used up! Current password?: your password Password changed to: Something else!

  First, we see our program print out our regular account’s password, followed by the new password after we change it ➊. That was too easy!

  Next, at ➋, we see that our secret account correctly hides the password from prying eyes, printing out only The password is secret! if we try to look at it.

  Finally, we try to change our secret account password at ➌. We put in two wrong passwords (lasers and ninja) before finally entering the correct password, your password, and our Ruby program prints out that we successfully updated our password to Something else!.

  Feel free to play around with the code. What happens when you try to get and set the password on the regular account in secrecy.rb? What about when you try to change the super_safe one?

  What happens if we try to set the password on our super_safe account and pass in the correct current password? The wrong one? Try passing in the wrong password a bunch of times. What happens?

  Once you’re done exploring the code, you can try thinking about all the cool stuff we could do to make it even better. For example, what methods could we add to the Account or SuperSecretAccount to make them even more useful? (Maybe a reset_password method, in case you’ve completely forgotten your password?) What methods might SuperSecretAccount override from Account? Are there any that might use some of the functionality of Account but not all of it? How could we go about doing that? (Hint: super would be involved.)

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Add Fast Bookmark
Load Fast Bookmark
Turn Navi On
Turn Navi On
Turn Navi On
Scroll Up
Turn Navi On
Scroll
Turn Navi On
183