Ruby wizardry, p.13

Ruby Wizardry, page 13

 

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  

  >> merlin.cast_spell('Prophecy') Cast Prophecy! Spells left: 4. => nil >> fumblesnore.cast_spell('Nap') Cast Nap! Spells left: 3. => nil

  “So when you create a class variable, there’s just one copy for the whole class, and any instances you create all share that one class variable?” Ruben asked.

  “That’s right,” said the Dagron.

  “It’s kind of weird that all weezards share a fixed group of spells, isn’t it?” asked Wherefore. “Wouldn’t it make sense for each weezard to have his own set of spells?”

  Instance Variables

  The Dagron nodded. “Sometimes it makes sense for the class that creates objects to keep track of certain information, but not all that often,” she said. “For that reason, we don’t end up using a lot of class variables in Ruby; it’s mostly instance and local variables. In fact, with instance variables, we can give each weezard her own set of spells,” the Dagron continued, and more code appeared on the screen. “An instance variable can be seen from inside the class and by any instance of the class, just like class variables. The big difference is that each instance gets its very own copy of the variable!”

  weezard_2.rb

  class Weezard def initialize(name, power='Flight') @name = name @power = power @spells = 5 end def cast_spell(name) if @spells > 0 @spells -= 1 puts "Cast #{name}! Spells left: #{@spells}." else puts 'No more spells!' end end end

  “See how we’ve moved the @@spells variable from a variable that belongs to the class to a @spells instance variable inside the initialize method?” asked the Dagron. “Variables that start with @ are instance variables. They’re called instance variables because each instance, which is what Ruby calls an object created by a class, has its own copy.”

  “So when we create instances of the Weezard class with the new method, each instance will get assigned its own @spells variable?” Scarlet asked.

  “Precisely,” said the Dagron. “In fact, let’s do that now. We’ll create our weezards, just as we did before.”

  >> load 'weezard_2.rb' => true >> merlin = Weezard.new('Merlin', 'Sees the future') => # >> fumblesnore = Weezard.new('Fumblesnore', 'Naps') => #

  “This looks just like it did the last time we created weezards!” grumped the King.

  “It’s very similar,” admitted the Dagron, “but there is one important difference. Look what happens when each weezard casts a spell!”

  >> merlin.cast_spell('Prophecy') Cast Prophecy! Spells left: 4. => nil >> fumblesnore.cast_spell('Nap') Cast Nap! Spells left: 4. => nil

  “They each have their own @spells variable!” said Scarlet. “That’s why fumblesnore’s spell count wasn’t affected when merlin cast a spell.”

  “Exactly right,” said the Dagron. “Even though their @spells variables have the same names, each instance gets its own copy, so they don’t conflict with each other. Not only that, but because instances of classes can always access their instance variables, any instance variables we define in our classes’ initialize method can be used by the newly created objects.”

  “That’s why we do things like @name = name in our initialize method definitions,” said the Off-White Knight. “It makes sure that when we pass in the name argument, each instance saves a copy in @name.”

  Local Variables

  “Speaking of local variables,” said the Dagron, “let’s have a look at those, shall we? They should be quite familiar, but they’re worth a second look. A local variable can be seen only in its current scope, which means it can be seen only in the method or class where it’s defined.”

  New code appeared on the Computing Contraption’s screen:

  >> class YeOldeClass >> local_variable = 'I only exist inside the class!' >> end >> puts local_variable NameError: undefined local variable or method `local_variable' for main:Object >> def yet_another_method >> another_local = 'I only exist inside this method!' >> end >> puts another_local NameError: undefined local variable or method `another_local' for main:Object

  “So really, local variables can be seen only inside the methods or classes where they’re defined, or we can use them outside all class and method definitions,” Scarlet said.

  “That’s right,” said the Dagron. “There is a special scope in Ruby called the top-level scope, so if you define local variables outside any method or class definition, Ruby can see them. Have a look!”

  >> local_variable = "I'm the top-level local variable!" >> def local_in_method >> local_variable = "I'm the local variable in the method!" >> puts local_variable >> end >> puts local_variable I'm the top-level local variable! => nil >> local_in_method I'm the local variable in the method! => nil

  “You see?” said the Dagron. “Local variables can even have the exact same variable names, as long as they’re in different scopes! Ruby knows that the method definition gets its own set of local variables, so it doesn’t complain that there are two with the same name.”

  “So local variables can be seen only in the classes or methods where we define them, or in this special top-level scope that’s outside any class or method,” said the King. “But global variables can be seen anywhere, and if we create an instance of a class, the instance can see any instance variables we created when we defined the class.”

  “Precisely,” said the Dagron.

  “And the class can see its own class variables,” the King continued.

  “Correct!” said the Dagron. “In fact, it’s not just instances that can have methods like initialize, introduce, and sing; even classes can have their own methods!”

  “Just when I was starting to get this!” moaned the King. “How is that possible?”

  “Because,” replied the Dagron, “Ruby classes are also objects!”

  “I need to sit down,” said the King.

  “You are sitting down,” said Wherefore.

  “So I am,” said the King, who was sitting cross-legged between the Off-White Knight and the Wand’ring Minstrel. “Go on, Madame Dagron,” he said. “How can we add a method directly to a class itself, and not just an instance of a class?”

  Objects and self

  “Well,” said the Dagron, “Ruby keeps a special built-in variable named self around at all times, and self refers to whatever Ruby object we’re currently talking about.” She was talking quickly now, and small sparks leapt from her mouth as she continued. “So all we need to do is use self to define a method inside our class, and instead of adding that method to the instance, we add it to the class itself.”

  “Perhaps an example would make things clearer,” said the Off-White Knight. She reached over and typed into the Computing Contraption:

  monkey.rb

  class Monkey @@number_of_monkeys = 0 def initialize @@number_of_monkeys += 1 end def self.number_of_monkeys @@number_of_monkeys end end

  “Here I’ve created a Monkey class,” said the knight. “It has a @@number_of_monkeys class variable that will keep track of how many monkey instances we create, as well as the initialize method we’ve seen in classes before. When we call new on Monkey to create a new monkey, it will add 1 to the @@number_of_monkeys.”

  “What about that self.number_of_monkeys method?” asked Ruben.

  “That’s a class method!” said the knight. “It’s a method of the Monkey class itself, and when we call it, it will return the @@number_of_monkeys. Let’s have a look! First, we’ll load that script, and then we’ll create a few monkeys.”

  >> load 'monkey.rb' => true >> monkey_1 = Monkey.new => # >> monkey_2 = Monkey.new => # >> monkey_3 = Monkey.new => #

  “Good!” said the Off-White Knight. “Now that we have some monkeys, let’s ask the Monkey class how many monkeys there are.” She typed into the Computing Contraption:

  >> Monkey.number_of_monkeys => 3

  “Amazing!” said Wherefore. “But why not ask an individual monkey how many monkeys there are?”

  “Well,” said the knight, “first, it doesn’t quite make sense to ask a monkey instance how many other instances there are—that’s the class’s business, not the instance’s! But more importantly, because we used self when we defined the number_of_monkeys method, it’s only a method of the class, not its instances! See?” She typed some more:

  >> monkey_1.number_of_monkeys NoMethodError: undefined method `number_of_monkeys' for #

  “There we are,” said the Dagron. “The Monkey class has its very own number_of_monkeys method now, but it’s only on the class itself; the monkey instances themselves don’t have that method.”

  “In fact,” said the knight, “adding methods onto classes is common enough that Ruby has its own shorter syntax for it. It looks like this!” And she typed some more:

  monkey_2.rb

  class Monkey @@number_of_monkeys = 0 def initialize @@number_of_monkeys += 1 end class << self def number_of_monkeys @@number_of_monkeys end end end

  “See that?” she asked. “Instead of defining the number_of_monkeys method on the class with self.number_of_monkeys, I used class << self to tell Ruby: ‘Hey! Every method I define until I say end is a method for the class, not its instances.’ Look what happens when I call the method on Monkey without creating any instances.”

  >> load 'monkey_2.rb' => true >> Monkey.number_of_monkeys => 0

  “Now look what happens if I create an instance and call the method again,” said the knight.

  >> monkey = Monkey.new => # >> Monkey.number_of_monkeys => 1

  “See? It’s just like using self.number_of_monkeys,” the Off-White Knight said, beaming.

  “Very interesting,” said the Dagron. “I’d never seen class << self before.”

  “Really?” asked Wherefore.

  “No one knows everything,” said the Dagron. “Not even me!”

  “Many people find the def self.method_name syntax easier to understand,” said the knight, “so it’s perfectly fine to use that whenever you need to add a method to a class.”

  “Of course,” said Scarlet, “and now self makes so much more sense to me! It just refers to whatever the Ruby program is ‘talking about.’ And in this case, self is the class we’re inside!”

  Methods and Instance Variables

  “Precisely,” said the Dagron. “And with that, I have but one more trick to show you. You see, while it’s very easy to create instance variables for our instances, it’s not always so easy to get at them. See what I mean?” she said, and as she spoke, new code began to fill the screen:

  >> class Minstrel >> def initialize(name) >> @name = name >> end >> end

  “I’ve re-created our Minstrel class from before, but with only an initialize method,” said the Dagron. “No introduce or sing methods! Let’s create an instance, like we did earlier.”

  >> wherefore = Minstrel.new('Wherefore') => #

  “Now,” said the Dagron, “see how our minstrel instance has the name ‘Wherefore’? (You can tell by the @name="Wherefore" bit.) Let’s try to get to it.”

  >> wherefore.name NoMethodError: undefined method `name' for #

  “You see,” said the Dagron, “while wherefore has a @name instance variable, it doesn’t have a name method. And when it comes to Ruby, all that matters are methods. In order to make wherefore.name actually work, we need to write a method to reach the @name instance variable.”

  “Does that mean we’ll need to define a method in the Minstrel class called name?” Scarlet asked.

  “That’s exactly right,” said the Dagron, and the code on the screen changed under her claw:

  another_minstrel.rb

  class Minstrel def initialize(name) @name = name end def name @name end end

  “Now we have a name method that returns the @name instance variable,” said the Dagron. “Let’s see what happens when we create a new minstrel with this name method and try to use it!”

  >> load 'another_minstrel.rb' => true >> wherefore = Minstrel.new('Wherefore') => # >> wherefore.name => "Wherefore"

  “Huzzah!” cried the King. “We’ve done it! We’ve changed the minstrel’s name with the name method.”

  “Truly wonderful,” said Wherefore, “but what if we want to change the minstrel’s name to something else?”

  “Well,” said the Dagron, “let’s see if we can do that with the code we have now.” She added more code to the Computing Contraption’s glowing screen:

  >> wherefore.name = 'Stinky Pete' NoMethodError: undefined method `name=' for #

  “We can get the name,” said the Dagron, “but we can’t change it; Ruby’s complaining that our instance has no method that changes names. It’s looking for a method we haven’t written yet!”

  Ruben studied the screen. “It’s that NoMethodError again,” he said. “It looks like Ruby wants the Minstrel class to have a method called name=!”

  The Dagron nodded. “If we want to change the @name, we have to write a special method called name= to do it,” she said. “If you write the name of a method with an equal sign at the end, Ruby understands it to mean: ‘I want this method to change the value of something.’ So to change the @name,” she finished, “we’d add a bit more code.”

  She added the name= method to the rest of the code for all of them to see:

  another_minstrel_2.rb

  class Minstrel def initialize(name) @name = name end def name @name end def name=(new_name) @name = new_name end end

  “Now we have a new method, name=, that takes a single parameter, the new_name,” said the Dagron. “This should tell Ruby to let us change the name simply by calling wherefore.name = 'some new name'! Let’s give it a try. First, we’ll create a new minstrel.”

  >> load 'another_minstrel_2.rb' => true >> wherefore = Minstrel.new('Wherefore') => # >> wherefore.name => "Wherefore"

  “Next, we’ll try to change its name.”

  >> wherefore.name = 'Stinky Pete' => "Stinky Pete" >> wherefore.name => "Stinky Pete"

  “That’s amazing!” said Ruben. “But writing all these methods to get and set instance variables sure is hard work. Is there any faster way to do it?”

  The Dagron nodded. “As it turns out, there is,” she said. “There are three built-in shortcut methods for reading and writing instance variables: attr_reader, attr_writer, and attr_accessor. Here’s how they work.” She touched the Computing Contraption with her claw, and these words appeared:

  another_minstrel_3.rb

  class Minstrel attr_accessor :name attr_reader :ballad def initialize(name) @name = name @ballad = 'The Ballad of Chucky Jim' end end

  “For example, if you pass the symbol :name to attr_reader, it will automatically create a method called name that will read the instance variable @name. attr_writer will automatically create a method called name= that will change the value of @name, and attr_accessor will create both the name and name= methods.” The Dagron clicked her claws. “In this case, I’ve called attr_accessor with :name and attr_reader with :ballad, which should mean I can both get and change the minstrel’s name, but can only read his ballad without changing it. Let’s create a new minstrel to test out.”

  >> load 'another_minstrel_3.rb' => true >> wherefore = Minstrel.new('Wherefore') => #

  “Perfect,” said the Dagron. “Let’s see if attr_accessor lets us get and change the minstrel’s name, like we could before.”

  >> wherefore.name => "Wherefore" >> wherefore.name = 'Wherefive' => "Wherefive" >> wherefore => #

  “Now let’s see if we can read the minstrel’s ballad, but not change it; that’s what attr_reader is supposed to do,” said the Dagron. She filled in more code on the Computing Contraption:

  >> wherefore.ballad => "The Ballad of Chucky Jim" >> wherefore.ballad = 'A Song of Mice and Friars' NoMethodError: undefined method `ballad=' for #

  Wherefore shook his head in amazement. “Extraordinary!” he said. “With these Ruby tools, I’ll be able to write ballads in no time at all.”

  “This is one of the most amazing parts of Ruby,” said the Off-White Knight. “When we design programs around objects, we’re doing something called object-oriented programming, and it lets us write programs that describe real-life things like minstrels and ballads. Everything becomes a thousand times easier!”

  “This is marvelous, truly marvelous,” said Wherefore. “I can’t thank you enough. How can I possibly repay you?”

  “Well,” Scarlet said, “actually, we were looking for you to ask whether you’d seen anything unusual happening in the kingdom. Ruby systems all over the kingdom have been breaking all day, and we’re starting to think none of the problems is an accident.”

  “Show him the scale!” Ruben said.

  “Oh, yeah!” Scarlet said, and she pulled the glittering green scale from her pocket. “Have you ever seen anything like this? We thought at first it might have belonged to the Dagron, but we checked and she isn’t missing a one.”

  “Hmm,” said Wherefore. “Quite the quandary. No, I don’t think I’ve seen any creature with scales like this, but I did see something strange out here in the Pines not an hour ago.”

  The King, Ruben, and Scarlet exchanged startled looks.

  “What was it?” Scarlet asked.

  “Well,” said Wherefore, “I only caught a snippet of conversation, but it was a few voices, talking in low tones behind that thicket yonder. I went to see what was going on, but they ran when I got near—three, maybe four of them,” he said.

 

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