Chapter 2. Getting Started with Ruby

I first found Ruby because of my eight-year-old son and his love of a certain electrically charged, but very sweet yellow mouse. Back in 2002, my son was spending his free time playing a certain computer game that involved finding and taming various magical creatures, including the energetic rodent.[1] In my mind’s eye, I see the glowing light bulb suddenly appear over his head. “My dad,” I imagine him thinking, “is a programmer. This thing that I’m playing, this thing with the magical islands and wonderful creatures, is a program. My dad makes programs. My dad can teach me to make a game!”

Well, maybe. After a blast of begging and pleading that only the parents of young children can truly comprehend, I set out to teach my son to program. The first thing I needed, I thought, was a simple, clear, and easy-to-understand programming language. So I went looking—and I found Ruby. My son, as kids do, rapidly moved on to other things. But in Ruby I had found a keeper. Yes, it was clean and clear and simple—a good language for learning. But the professional software engineer in me, the guy who has built systems in everything from assembly language to Java, saw something else: Ruby is concise, sophisticated, and wickedly powerful.

But Ruby is also solidly mainstream. The basic moving parts of the language are the old gears and pulleys familiar to all programmers. Ruby has all of the data types with which you are familiar: strings, integers, and floating-point numbers, along with arrays and our old friends, true and false. The basics of Ruby are familiar and commonplace. It is the way they are assembled and the higher-level stuff that makes the language such an unexpected joy.

If you already know the basics of Ruby, if you know how to pull the third character out of a string and how to raise 2 to the 437th power, and if you have written a class or two, then you can probably dive right into Chapter 3. This chapter will still be here if you get stuck.

If you are very new to Ruby, then this chapter is for you. In this chapter I will take you through the basics of the language as quickly as possible. After all, Alan Turing was right: Past a certain level of complexity, all programming languages are equivalent. If you already know another mainstream language, then the basics of Ruby will present no problem to you; you will just be relearning all of the things that you already know. My hope is that by the end of this chapter you will know just enough of the language to be dangerous.

Interactive Ruby

The easiest way to run Ruby[2] is to use the interactive Ruby shell, irb. Once you start up irb, you can type in Ruby code interactively and see the results immediately. To run irb, you simply enter irb at the command prompt. The following example starts up irb and then uses Ruby to add 2 plus 2:


   $ irb
   irb(main):001:0> 2 + 2
   => 4
   irb(main):002:0>


The interactive Ruby shell is great for trying out things on a small scale. There is nothing like immediate feedback for exploring a new programming language.

Saying Hello World

Now that you have Ruby safely up and running, the next step is obvious: You need to write the familiar hello world program. Here it is in Ruby:


   #
   # The traditional first program in any language
   #
   puts('hello world')


You could simply start up irb and type in this code interactively. Alternatively, you could use a text editor and type the hello world program into a text file with a name such as hello.rb. You can then use the Ruby interpreter, appropriately called ruby, to run your program:

   $ ruby hello.rb

Either way, the result is the same:

   hello world

You can learn an awful lot about a programming language from the hello world program. For example, it looks like the puts method prints things out, and so it does. You can also see that Ruby comments start with the # character and continue until the end of the line. Comments may fill the entire line, as in the earlier example, or you can tack on a comment after some code:

   puts('hello world')    # Say hello

Another thing to notice about our Ruby hello world program is the absence of semicolons to end the statements. In Ruby, a program statement generally ends with the end of a line. Ruby programmers tend to use semicolons only to separate multiple statements on the same line on those rare occasions when they decide to jam more than one statement on a line:


   #
   # A legal, but very atypical Ruby use of the semicolon
   #
   puts('hello world'),
   #
   # A little more de rigueur, but still rare use of semicolons
   #
   puts('hello '), puts('world')


The Ruby parser is actually quite smart and will look at the next line if your statement is obviously unfinished. For example, the following code works just fine because the Ruby parser deduces from the dangling plus sign that the calculation continues on the second line:


   x = 10 +
     20 + 30


You can also explicitly extend a statement onto the next line with a backslash:


   x = 10
       + 10


There is a theme here: The Ruby design philosophy is to have the language help you whenever possible but to otherwise stay out of the way. In keeping with this philosophy, Ruby lets you omit the parentheses around argument lists when the meaning is clear without them:

   puts 'hello world'

For clarity, most of the examples in this book include the parentheses when calling a method, unless there are no arguments, in which case the empty parentheses are omitted.

While our hello world program wrapped its string with single quotes, you can also use double quotes:

   puts("hello world")

Single or double quotes will result in exactly the same kind of string object, albeit with a twist. Single-quoted strings are pretty much a case of “what you see is what you get”: Ruby does very little interpretation on single-quoted strings. Not so with double-quoted strings. These strings are preprocessed in some familiar ways: becomes a single linefeed (also known as a newline) character, while becomes a tab. So while the string 'abc ' is five characters long (the last two characters being a backslash and the letter “n”), the string "abc " is only four characters long (the last character being a linefeed character).

Finally, you may have noticed that no appeared at the end of the 'hello world' string that we fed to puts. Nevertheless, puts printed our message with a newline. It turns out that puts is actually fairly clever and will stick a newline character on the end of any output that lacks one. This behavior is not necessarily desirable for precision formatting, but is wonderful for the kind of examples you will find throughout this book.

Variables

Ordinary Ruby variable names—we will meet several extraordinary variables in a bit—start with a lowercase letter or an underscore.[3] This first character can be followed by uppercase or lowercase letters, more underscores, and numbers. Variable names are case sensitive, and other than your patience there is no limit on the length of a Ruby variable name. All of the following are valid Ruby variable names:

  • max_length
  • maxLength
  • numberPages
  • numberpages
  • a_very_long_variable_name
  • _flag
  • column77Row88
  • ___

The first two variable names in this list, max_length and maxLength, bring up an important point: While camelCase variable names are perfectly legal in Ruby, Ruby programmers tend not to use them. Instead, the well-bred Ruby programmer uses words_separated_by_underscores. Also, because variable names are case sensitive, numberPages and numberpages are different variables. Finally, the last variable name listed above consists of three underscores, which is legal in the way that many very bad ideas are legal.[4]

Okay, let’s put our strings and variables together:


   first_name = 'russ'
   last_name = 'olsen'
   full_name = first_name + ' ' + last_name


What we have here are three basic assignments: The variable first_name is assigned the string 'russ', last_name is assigned a value of 'olsen', and full_name gets the concatenation of my first and last names separated by a space.

You may have noticed that none of the variables were declared in the previous example. There is nothing declaring that the variable first_name is, and always will be, a string. Ruby is a dynamically typed language, which means that variables have no fixed type. In Ruby, you simply pull a variable name out of thin air and assign a value to it. The variable will take on the type of the value it happens to hold. Not only that, but at different times in the same program a variable might hold radically different types. Early in a program, the value of pi might be the number 3.14159; later in the same program, the value might be a reference to a complex mathematical algorithm; still later, it might be the string "apple". We will revisit dynamic typing a number of times in this book (starting with the very next chapter, if you can’t wait), but for now just remember that variables take on the types of their values.

Aside from the garden-variety variables that we have examined so far, Ruby supports constants. A constant is similar to a variable, except that its name starts with an uppercase letter:


   POUNDS_PER_KILOGRAM = 2.2
   StopToken = 'end'
   FACTS = 'Death and taxes'


The idea of a constant is, of course, that the value should not change. Ruby is not overly zealous about enforcing this behavior, however. You can change the value of a constant, but you will get a warning for your trouble:


   StopToken = 'finish'
   (irb):2: warning: already initialized constant StopToken


For the sake of sanity, you should probably avoid changing the values of constants.

Fixnums and Bignums

To no one’s great surprise, Ruby supports arithmetic. You can add, subtract, multiply, and divide in Ruby with the usual results:


   x = 3
   y = 4
   sum = x+y
   product = x*y


Ruby enforces some standard rules about numbers. There are two basic flavors, integers and floating-point numbers. Integers, of course, lack a fractional part: 1, 3, 6, 23, –77, and 42 are all integers, whereas 7.5, 3.14159, and –60000.0 are all floating-point numbers.

Integer division in Ruby comes as no surprise: Divide two integers and your answer will be another integer, with any decimal part of the quotient silently truncated (not rounded!):


   6/3            # Is 2
   7/3            # Is still 2
   8/3            # 2 again
   9/3            # Is 3 finally!


Reasonably sized integers—anything you can store in 31 bits—are of type Fixnum, while larger integers are of type Bignum; a Bignum can hold an arbitrarily gigantic number. Given that the two types convert back and forth more or less seamlessly, however, you can usually forget that there is any distinction between them:


   2                     # A Fixnum
   437                   # A Fixnum
   2**437                # Very definitely a big Bignum
   1234567890            # Another Bignum
   1234567890/1234567890 # Divide 2 Bignums, and get 1, a Fixnum


Ruby also supports the familiar assignment shortcuts, which enable you to shorten expressions such as a = a+1 to a += 1:


   a = 4
   a += 1       # a is now 5
   a -= 2       # a is now 3
   a *= 4       # a is now 12
   a /= 2       # a is now 6


Sadly, there are no increment (++) and decrement (--) operators in Ruby.

Floats

If only the world were as exact as the integers. To deal with messy reality, Ruby also supports floating-point numbers or, in Ruby terminology, floats. A float is easy to spot—it is a number with a decimal point:


   3.14159
   -2.5
   6.0
   0.0000000111


You can add, subtract, and multiply floats with the results you would expect. Floats even obey the more familiar rules of grammar-school division:


   2.5+3.5        # Is 6.0
   0.5*10         # Is 5.0
   8.0/3.0        # Is 2.66666666


There Are No Primitives Here

You do not have to take my word about the types of these different flavors of numbers. You can simply ask Ruby by using the class method:


   7.class               # Gives you the class Fixnum
   888888888888.class    # Gives you the class Bignum
   3.14159.class         # Gives you the class Float


The slightly strange-looking syntax in this code is actually a tip-off to something deep and important: In Ruby, everything—and I mean everything—is an object. When we say 7.class, we are actually using the familiar object-oriented syntax to call the class method on an object, in this case the object representing the number seven. In fact, Ruby numbers actually have quite a wide selection of methods available:


   3.7.round         # Gives us 4.0
   3.7.truncate      # Gives us 3
   -123.abs          # Absolute value, 123
   1.succ            # Successor, or next number, 2


Unlike Java, C#, and many other widely used languages, Ruby does not have any primitive data types. It is objects all the way down. The fact that everything is an object in Ruby drives much of the elegance of the language. For example, the universal object orientation of Ruby is the secret that explains how Fixnums and Bignums can be so effortlessly converted back and forth.

If you follow the class inheritance hierarchy of any Ruby object upward, from its class up through the parent class or superclass, and on to its super-duper-class, eventually you will reach the Object class. Because every Ruby object can trace its class ancestry back to Object, all Ruby objects inherit a minimum set of methods, a sort of object-oriented survival kit. That was the source of the class method that we encountered earlier. We can also find out whether an object is an instance of a given class:

   'hello'.instance_of?(String)  # true

We can also see if it is nil:

   'hello'.nil?                  # false

Perhaps the Object method that gets the most use is to_s, which returns a string representation of the object—a suitably brief name for the Ruby equivalent of the Java toString method:


   44.to_s         # Returns a two-character string '44'
   'hello'.to_s    # A fairly boring conversion, returns 'hello'


The total object orientation of Ruby also has some implications for variables. Because everything in Ruby is an object, it is not really correct to say that the expression x = 44 assigns the value 44 to the variable x. Instead, what is really happening is that x receives a reference to an object that happens to represent the number after 43.

But Sometimes There Is No Object

If everything is an object, what happens when you do not really have an object? For exactly those occasions, Ruby supplies us with a special object that represents the idea of not having an object, of being utterly object-less, sans object. This special value is nil.

In the last section, we said that everything in Ruby is an object, and so it is: nil is every bit as much a Ruby object as "hello world" or 43. For example, you can get the class of nil:

   puts(nil.class)

That turns out to be something very predictable:

   NilClass

Sadly, nil is destined to live out its life alone: There is only one instance of NilClass (called nil) and you cannot make any new instances of NilClass.

Truth, Lies, and nil

Ruby supports the usual set of Boolean operators. For example, we can find out if two expressions are equal, if one expression is less than the other, or if one expression is greater than the other.


   1 == 1               # true
   1 == 2               # false
   'russ' == 'smart'    # sadly, false
   (1 < 2)              # true
   (4 > 6)              # nope
  
   a = 1
   b = 10000
   (a > b)              # no way


We also have less than or equal and its cousin, greater than or equal:


   (4 >= 4)                # yes!
   (1 <= 2)                # also true


All of these comparison operators evaluate to one of two objects—true or false. Like nil, the true and false objects are the only instances of their respective classes: true is the only instance of TrueClass and false is the sole instance of (you guessed it) FalseClass. Oddly, both TrueClass and FalseClass are direct subclasses of Object. I keep expecting a BooleanClass to slip in somewhere, but alas, no.

Ruby also has an and operator—in fact, several of them. For example, you might say


   (1 == 1) and (2 == 2)    # true
   (1 == 1) and (2 == 3)    # false


You might also say


   (1 == 1) && (2 == 2)     # true
   (1 == 1) && (2 == 3)     # false


Both amount to the same thing. Essentially, and and && are synonyms.[5]

Matched up with and and && is or and ||, which do about what you would expect:[6]


   (1 == 1) or (2 == 2)     # yup
   (2 == 1) || (7 > 10)     # nope
   (1 == 1) or (3 == 2)     # yup
   (2 == 1) || (3 == 2)     # nope


Finally, Ruby has the usual not operator and its twin !:


   not (1 == 2)  # true
   ! (1 == 1)    # false
   not false     # true


One thing to keep in mind is that Ruby can come up with a Boolean value for any expression. We can, therefore, mix strings, integers, and even dates into Boolean expressions. The evaluation rule is very simple: false and nil evaluate to false; everything else evaluates to true. So the following are perfectly legal expressions:


   true and 'fred'  # true, because 'fred' is neither nil nor false
   'fred' && 44     # true, because both 'fred' and 44 are true
   nil || false     # false, because both nil and false are false


If you come from the world of C or C++, you will be shocked to learn that in Ruby, zero, being neither false nor nil, evaluates to true in a Boolean expression. Surprisingly, this expression


   if 0
     puts('Zero is true!')
   end


will print out

   Zero is true!

Decisions, Decisions

That last example was a sneak preview of the if statement, which has the usual optional else:


   age = 19
  
   if (age >= 18)
     puts('You can vote!')
   else
     puts('You are too young to vote.')
   end


As you can see from the example, each if statement always has its own terminating end. If you have more than one condition, you can use an elsif:


   if(weight < 1)
     puts('very light')
   elsif(weight < 10)
     puts('a bit of a load')
   elsif(weight < 100)
     puts('heavy')
   else
     puts('way too heavy')
   end


Note that the keyword is elsif—five letters, one word. It is not else if, elseif, and certainly not elif.

As usual, Ruby tries its best to make the code as concise as possible. Because the parentheses after the if and elsif really do not add much to the meaning, they are optional:


   if weight < 1
     puts('very light')
   elsif weight < 10
     puts('a bit of a load')
   elsif weight < 100
     puts('heavy')
   else
     puts('way too heavy')
   end


There is also an idiom for those times when you need to decide whether you want to execute a single statement. Essentially, you can hang the if on the end of a statement:

   puts('way too heavy') if weight >= 100

There is also an unless statement, which reverses the sense of an if statement: The body of the statement executes only if the condition is false. As with the if statement, you can have a long form of unless:


   unless weight < 100
     puts('way too heavy')
   end


A short form is also available:

   puts('way too heavy') unless weight < 100

Loops

Ruby has two flavors of loops. First, we have the classic while loop, which, like the if statement, is always terminated with an end. Thus this loop


   i = 0
   while i < 4
     puts("i = #{i}")
     i = i + 1
   end


will print out this:


   i = 0
   i = 1
   i = 2
   i = 3


The evil twin of the while loop is the until loop, which is more or less identical to the while loop except that it keeps looping until the condition becomes true. Thus we might have written the preceding example as follows:


   i = 0
   until i >= 4
     puts("i = #{i}")
     i = i + 1
   end


Ruby also has a for loop, which you can use, among other things, to sequence through arrays:


   array = ['first', 'second', 'third']
   for element in array
     puts element
   end


Surprisingly, for loops are rare in real Ruby programs. A Ruby programmer is much more likely to write this equivalent code instead:


   array.each do |x|
     puts(x)
   end


We will have much more to say about this odd-looking loop thing in Chapter 7. For now, just think of the each syntax as another way to write a for loop.

If you need to break out of a loop early, you can use a break statement:


   names = ['george', 'mike', 'gary', 'diana']
  
   names.each do |name|
     if name == 'gary'
       puts('Break!')
       break
     end
     puts(name)
   end


Run this code and it will never print out gary:


   george
   mike
   Break!


Finally, you can skip to the next iteration of a loop with the next statement:


   names.each do |name|
     if name == 'gary'
       puts('Next!')
       next
     end
     puts(name)
   end


This code will skip right over gary but keep going:


   george
   mike
   Next!
   diana


More about Strings

Since we are already using Ruby strings, let’s get to know them a little better. As we have seen, we can build string literals with both single and double quotes:


   first = 'Mary had'
   second = " a little lamb"


We have also seen that the plus sign is the concatenation operator, so that

   poem = first + second

will give us this:

   Mary had a little lamb

Strings have a whole range of methods associated with them. You can, for example, get the length of a string:

   puts(first.length)     # Prints 8

You can also get an all-uppercase or all-lowercase version of a string:


   puts(poem.upcase)
   puts(poem.downcase)


This code will print out


   MARY HAD A LITTLE LAMB
   mary had a little lamb


In many ways, Ruby strings act like arrays: You can set individual characters in a string by indexing the string in a very array-like fashion. Thus, if you execute


   poem[0] = 'G'
   puts(poem)


you will get a very different sort of poem:

   Gary had a little lamb

You can also get at individual characters in a string in the same way, albeit with a slightly annoying twist: Because Ruby lacks a special character type, when you pull an individual character out of a Ruby string you get a number—namely, the integer character code. Consider this example:

   second_char = poem[1]    # second_char is now 97, the code for 'a'

Fortunately, you can also put individual characters back via the code, so perhaps there is not much harm done:

   poem[0] = 67             # 67 is the code for 'C'

Now Cary is the former owner of a young sheep.

Double-quoted strings in Ruby have another feature, one that you are going to run into frequently in the examples in this book. While it is turning the familiar 's into newlines and 's into tabs, if the Ruby interpreter sees #{expression} inside a double-quoted string, it will substitute the value of the expression into the string. For example, if we set the variable n

   n = 42

we can smoothly insert it into a string

   puts("The value of n is #{n}.")

to get

   The value of n is 42.

This feature (called string interpolation) is not just limited to one expression per string, nor is the expression limited to a simple variable name. For example, we can say


   city = 'Washington'
   temp_f = 84
   puts("The city is #{city} and the temp is #{5.0/9.0 * (temp_f-32)} C")


which will print

   The city is Washington and the temp is 28.8888888888889 C

While traditional quotes are great for relatively short, single-line strings, they tend to be awkward for expressing longer, multiple-line strings. To ease this pain, Ruby has another way of expressing string literals:


   a_multiline_string = %Q{
   The city is #{city}.
   The temp is #{5.0/9.0 * (temp_f-32)} C
   }


In this example, everything between the %Q{ and the final } is a string literal. If you start your string literal with a %Q{ as we did above, Ruby will treat your string as double quoted and do all of the usual double-quoted interpretation on it. If you use %q{ (note the lowercase "q"), your text will receive the same minimal processing as a single-quoted string.[7]

Finally, if you are coming from the Java or C# world, there is a serious conceptual landmine waiting for you in Ruby. In C# and Java, strings are immutable: Once you create a string in either of these languages, that string can never be changed. Not so in Ruby. In Ruby, any string is liable to change just about any time. To illustrate, let us create two references to the same string:


   name = 'russ'
   first_name = name


If this were Java or C# code, I could use first_name essentially forever, serene in the knowledge that its value could never change out from under me. By contrast, if I change the contents of name:

   name[0] = 'R'

I also change first_name, which is just a reference to the same string object. If we print out either variable


   puts(name)
   puts(first_name)


we will get the same, changed value:


   Russ
   Russ


Symbols

The merits of making strings immutable have been the subject of long debate. Strings were mutable in C and C++, were immutable in Java and C#, and went back to mutable in Ruby. Certainly there are advantages to mutable strings, but making strings mutable does leave an obvious gap: What do we do when we need to represent something that is less about data and more like an internal identifier in our program?

Ruby has a special class of object just for this situation—namely, the symbol. A Ruby symbol is essentially an immutable identifier type thing. Symbols always start with a colon:

  • :a_symbol
  • :an_other_symbol
  • :first_name

If you are not used to them, symbols may seem a bit strange at first. Just remember that symbols are more or less immutable strings and that Ruby programmers use them as identifiers.

Arrays

Creating arrays in Ruby is as easy as typing a pair of square braces or Array.new:


   x = []                               # An empty array
   y = Array.new                        # Another one
   a = [ 'neo',  'trinity',  'tank']    # A three-element array


Ruby arrays are indexed from zero:


   a[0]                                 # neo
   a[2]                                 # tank


You can get the number of elements in an array with the length or size method. Both do the same thing:


   puts(a.length)                       # Is 3
   puts(a.size)                         # Is also 3


Keep in mind that Ruby arrays do not have a fixed number of elements. You can extend arrays on the fly by simply assigning a new element beyond the end of the array:

   a[3] = 'morpheus'

Our array now contains four elements.

If you add an element to an array far beyond the end, then Ruby will automatically add the intervening elements and initialize them to nil. Thus, if we execute the code


   a[6] = 'keymaker'
   puts(a[4])
   puts(a[5])
   puts(a[6])


we will get


   nil
   nil
   keymaker


A convenient way to append a new element to the end of an array is with the << operator:

   a << 'mouse'

Ruby also sticks to the spirit of dynamic typing with arrays. In Ruby, arrays are not limited to a single type of element. Instead, you can mix and match any kind of object in a single array:

   mixed = ['alice', 44, 62.1234, nil, true, false]

Finally, because arrays are just regular objects,[8] they have a rich and varied set of methods. You can, for example, sort your array:


   a = [ 77, 10, 120, 3]
   a.sort               # Returns [3, 10, 77, 120]


You can also reverse the elements in an array:


   a = [1, 2, 3]
   a.reverse            # Returns [ 3, 2, 1]


Keep in mind that the sort and reverse methods leave the original array untouched: They actually return a new array that is sorted or reversed. If you want to sort or reverse the original array, you can use sort! and reverse!:


   a = [ 77, 10, 120, 3]
   a.sort!           # a is now [3, 10, 77, 120]
   a.reverse!        # a is now [120, 77, 10, 3]


This convention of method leaving the original object untouched while method! modifies the original object is not limited to arrays. It is applied frequently (but sadly not quite universally) throughout Ruby.

Hashes

A Ruby hash is a close cousin to the array—you can look at a hash as an array that will take anything for an index. Oh yes, and unlike arrays, hashes are unordered. You can create a hash with a pair of braces:


   h = {}
   h['first_name'] = 'Albert'
   h['last_name'] = 'Einstein'
  
   h['first_name']     # Is 'Albert'
   h['last_name']      # Is Einstein


Hashes also come complete with a shortcut initialization syntax. We could define the same hash with

   h = {'first_name' => 'Albert', 'last_name' => 'Einstein'}

Symbols make good hash keys, so our example might be improved with

   h = {:first_name => 'Albert', :last_name => 'Einstein'}

Regular Expressions

The final built-in Ruby type that we will examine is the regular expression. A regular expression in Ruby sits between a pair of forward slashes:


   /old/
   /Russ|Russell/
   /.*/


While you can do incredibly complex things with regular expressions, the basic ideas underlying them are really very simple and a little knowledge will take you a long way.[9] Briefly, a regular expression is a pattern that either does or does not match any given string. For example, the first of the three regular expressions above will match only the string 'old', while the second will match two variations of my first name. The third expression will match anything.

In Ruby, you can use the =~ operator to test whether a given regular expression matches a particular string. The =~ operator will return either nil (if the expression does not match the string) or the index of the first matching character (if the pattern does match):


   /old/ =~ 'this old house'  # 5 - the index of 'old'
   /Russ|Russell/ =~ 'Fred'   # nil - Fred is not Russ nor Russell
   /.*/ =~ 'any old string'   # 0 - the RE will match anything


There is also a !~ operator for testing whether a regular expression does not match a given string.

A Class of Your Own

Ruby would not be much of an object-oriented language if you could not create classes of your own:


   class BankAccount
     def initialize( account_owner )
       @owner = account_owner
       @balance = 0
     end
  
     def deposit( amount )
       @balance = @balance + amount
     end
  
     def withdraw( amount )
       @balance = @balance - amount
     end
   end


Clearly, the Ruby class definition syntax has the same unadorned brevity as the rest of the language. We start a class definition with the keyword class followed by the name of the class:

   class BankAccount

Recall that in Ruby all constants start with an uppercase letter. In Ruby’s world-view, a class name is a constant. This makes sense because the name of a class always refers to the same thing—the class. Thus, in Ruby, all class names need to start with an uppercase letter, which is why our class is BankAccount with a capital "B". Also note that while the only hard requirement is that the name of a class begin with an uppercase letter, Ruby programmers typically use camel case for class names.

The first method of our BankAccount class is the initialize method:


   def initialize( account_owner )
     @owner = account_owner
     @balance = 0
   end


The initialize method is both special and ordinary. It is ordinary in the way it is built—the line introducing the method consists of the keyword def followed by the name of the method, followed by the argument list, if there is one. Our initialize method does have a single argument, account_owner.

Next, we have the body of the method—in this case, a couple of assignment statements. The first thing our initialize method does is to grab the value passed in through account_owner and assign it to the very strange-looking variable, @owner:

        @owner = account_owner

Names that start with an @ denote instance variables—each instance of the BankAccount class will carry around its own copy of @owner. Likewise, each instance of BankAccount will carry around its copy of @balance, which we initialize to zero. As usual, there is no up-front declaration of @owner or @balance; we simply make up the names on the spot.

Although initialize is defined in the same, ordinary way as all other methods, it is special because of its name. Ruby uses the initialize method to set up new objects. When Ruby creates a new instance of a class, it calls the initialize method to set up the new object for use. If you do not define an initialize method on your class, Ruby will do the typical object-oriented thing: It will look upward through the class hierarchy until either it finds an initialize method or it reaches Object. Given that the Object class defines an initialize method (which does nothing), the search is guaranteed to quietly end there. Essentially, initialize is the Ruby version of a constructor.

To actually construct a new instance of our class, we call the new method on the class. The new method takes the same parameters as the initialize method:

   my_account = BankAccount.new('Russ')

This statement will allocate a new BankAccount object, call its initialize method with the arguments passed into new, and assign the newly initialized BankAccount instance to my_account.

Our BankAccount class has two other methods, deposit and withdraw, which grow and shrink the size of our account, respectively. But how do we get at our account balance?

Getting at the Instance Variables

While our BankAccount class seems like it is almost ready for use, there is one problem: In Ruby, an object’s instance variables cannot be accessed from outside the object. If we made a new BankAccount object and tried to get at @balance, we are in for an unpleasant shock. Running this code


   my_account = BankAccount.new('russ')
   puts(my_account.balance)


produces the following error:

   account.rb:8: undefined method 'balance' ... (NoMethodError)

Nor does my_account.@balance, with the at sign, work. No, the instance variables on a Ruby object are just not visible outside the object. What is a coder to do? We might simply define an accessor method:


   def balance
     @balance
   end


One thing to note about the balance method is that it lacks a return statement. In the absence of an explicit return statement, a method will return the value of the last expression computed, which in this case is simply @balance.

We can now get at our balance:

   puts(my_account.balance)

The fact that Ruby allows us to omit the parentheses for an empty argument list gives us the satisfying feeling that we are accessing a value instead of calling a method.

We might also like to be able to set the account balance directly. The obvious thing to do is to add a setter method to BankAccount:


   def set_balance(new_balance)
     @balance = new_balance
   end


Code with a reference to a BankAccount instance could then set the account balance:

   my_account.set_balance(100)

One problem with the set_balance method is that it is fairly ugly. It would be much clearer if you could just write

   my_account.balance = 100

Fortunately, you can. When Ruby sees an assignment statement like this one, it will translate it into a plain old method call. The method name will be variable name, followed by an equals sign. The method will have one parameter, the value of the right-hand side of the assignment statement. Thus the assignment above is translated into the following method call:

   my_account.balance=(100)

Take a close look at the name of that method. No, that is not some special syntax; the method name really does end in an equals sign. To make this all work for our BankAccount object, we simply rename our setter method:


   def balance=(new_balance)
     @balance = new_balance
   end


We now have a class that looks good to the outside world: Code that uses BankAccount can set and get the balance with abandon, without caring that it is really calling the balance and balance= methods. Sadly, our class is a bit verbose on the inside: We seem doomed to have all of these boring name and name= methods littered throughout our class definition. Unsurprisingly, Ruby comes to our rescue yet again.

It turns out that getter and setter methods are so common that Ruby supplies us with a great shortcut to create them. Instead of going through all of the def name . . . motions, we can simply add the following line to our class:

   attr_accessor :balance

This statement will create a method called balance whose body does nothing more than return the value of @balance. It will also create the balance= (new_value) setter method. We can even create multiple accessor methods in a single statement:

   attr_accessor :balance, :grace, :agility

The preceding code adds no less than six new methods for the enclosing class: getter and setter methods for each of the three named instance variables.[10] Instant accessors, no waiting.

You also have help if you want the outside world to be able to read your instance variables but not write them. Just use attr_reader instead of attr_accessor:

     attr_reader :name

Now your class has a getter method for name, but no setter method. Similarly, attr_writer will create only the setter method, name=(new_value).

An Object Asks: Who Am I?

Sometimes a method needs a reference to the current object, the instance to which the method is attached. For that purpose we can use self, which is always a reference to the current object:


   class SelfCentered
     def talk_about_me
       puts("Hello I am #{self}")
     end
   end
  
   conceited = SelfCentered.new
   conceited.talk_about_me


If you run this code, you will get something like this:

   Hello I am #<SelfCentered:0x40228348>

Of course, your instance of SelfCentered is unlikely to reside at the same hex address as mine, so your output may look a little different.

Inheritance, Subclasses, and Superclasses

Ruby supports single inheritance—all the classes that you create have exactly one parent or superclass. If you do not specify a superclass, your class automatically becomes a subclass of Object. If you want your superclass to be something other than Object, you can specify the superclass right after the class name:


   class InterestBearingAccount < BankAccount
     def initialize(owner, rate)
       @owner = owner
       @balance = 0
       @rate = rate
     end
  
     def deposit_interest
       @balance += @rate * @balance
     end
   end


Take a good look at the InterestBearingAccount initialize method. Like the initialize method of BankAccount, the InterestBearingAccount initialize method sets @owner and @balance along with the new @rate instance variable. The key point is that the @owner and @balance instance variables in InterestBearingAccount are the same as the ones in the BankAccount class. In Ruby, an object instance has only one set of instance variables, and those variables are visible all the way up and down the inheritance tree. If we went BankAccount mad and built a subclass of BankAccount and a sub-subclass of that, and so on for 40 classes and 40 subclasses, there would still be only one @owner instance variable per instance.

One unfortunate aspect of our InterestBearingAccount class is that the InterestBearingAccount initialize method sets the @owner and @balance fields, essentially duplicating the contents of the initialize method in BankAccount. We can avoid this messy code duplication by calling the BankAccount initialize method from the InterestBearingAccount initialize method:


   def initialize(owner, rate)
     super(owner)
     @rate = rate
   end


Our new initialize method replaces the duplicate code with a call to super. When a method calls super, it is saying, “Find the method with the same name as me in my superclass, and call that.” Thus the effect of the call to super in the initialize method is to call the initialize method in the BankAccount class. If there is no method of the same name in the superclass, Ruby will continue looking upward through the inheritance tree until it finds a method or runs out of classes, in which case you will get an error.

Unlike many object-oriented languages, Ruby does not automatically ensure that initialize is called for all your superclasses. In this sense, Ruby treats initialize like an ordinary method. If the InterestBearingAccount initialize method did not make the call to super, the BankAccount rendition of initialize would never be called on InterestBearingAccounts.

Argument Options

So far, the methods with which we have adorned our classes have sported pretty boring lists of arguments. It turns out that Ruby actually gives us a fair number of options when it comes to method arguments. We can, for example, specify default values for our arguments:


   def create_car( model, convertible=false)
     # ...
   end


You can call create_car with one argument—in which case convertible defaults to false—or two arguments. Thus all of the following are valid calls to create_car:


   create_car('sedan')
   create_car('sports car', true)
   create_car('minivan', false)


If you do write a method with default values, all of the arguments with default values must come at the end of the argument list.

While default values give you a lot of method-defining flexibility, sometimes even more freedom is handy. For those occasions you can create methods with an arbitrary number of arguments:


   def add_students(*names)
     for student in names
       puts("adding student #{student}")
     end
   end
  
   add_students( "Fred Smith", "Bob Tanner" )


Run the code above and you will see


   adding student Fred Smith
   adding student Bob Tanner


The add_students method works because all of the arguments are rolled up in the names array—that’s what the asterisk indicates. You can even mix and match regular arguments with the variable arguments array, as long as the array appears at the end of the argument list:


   def describe_hero(name, *super_powers)
     puts("Name: #{name}")
     for power in super_powers
       puts("Super power: #{power}")
     end
   end


The preceding method requires at least one argument but will take as many additional arguments as you care to give it. Thus all of the following are valid calls to describe_hero:


   describe_hero("Batman")
   describe_hero("Flash", "speed")
   describe_hero("Superman", "can fly", "x-ray vision", "invulnerable")


Modules

Along with classes, Ruby features a second code-encapsulating entity called a module. Like a class, a module is a package of methods and constants. Unlike a class, however, you can never create an instance of a module. Instead, you include a module in a class, which will pull in all of the module’s methods and constants and make them available to instances of the class. If you are a Java programmer, you might think of modules as being a bit like interfaces that carry a chunk of implementation code.

A module definition bears an uncanny resemblance to a class definition, as we can see from this simple, one-method module:


   module HelloModule
     def say_hello
       puts('Hello out there.')
     end
   end


Once we have defined our little module, we can pull it into any of our classes with the include statement:[11]


   class TryIt
     include HelloModule
   end


The effect of the include statement is to make all of the methods in the module available to instances of the class:


   tryit = TryIt.new
   tryit.say_hello


The accessibility also works in the other direction: Once a module is included in a class, the module methods have access to all of the methods and instance variables of the class. For example, the following module contains a method that prints various bits of information about the object in which it finds itself—values that it gets by calling the name, title, and department methods supplied by its host class:


   module Chatty
     def say_hi
       puts("Hello, my name is #{name}")
       puts("My job title is #{title}")
       puts("I work in the #{department} department")
     end
   end
  
   class Employee
     include Chatty
  
     def name
       'Fred'
     end
  
     def title
       'Janitor'
     end
  
     def department
       'Maintenance'
     end
   end


Running this code produces


   Hello, my name is Fred
   My job title is Janitor
   I work in the Maintenance department


When you include a module in your class, the module becomes a sort of special, secret superclass of your class (see Figure 2-1). But while a class can have only one superclass, it can include as many modules as it likes.

Figure 2-1. A module mixed into a class

image

When someone calls a method on an instance of your class, Ruby will first determine whether that method is defined directly in your class. If it is, then that method will be called. For example, if you call the name method on an Employee instance, Ruby will look first in the Employee class, see that a name method is available right there, and call it. If there is no such method defined directly by the class, Ruby will next look through all the modules included by the class. For example, if you call the say_hi methods, Ruby—after failing to find it in the Employee class itself—will look in the modules included by Employee. If the class includes more than one module, Ruby will search the modules from the last one included back to the first. But our Employee class includes only one module; right there in the Chatty module Ruby will find and call the say_hi method. If Ruby had not found the method in the Employee class or in any of its modules, it would have continued the search on to Employee superclass—and its modules.

Modules, when used in the way described here, are known as mixins—because they live to be mixed in (that is, to add their methods) to classes. Conceptually, mixin modules resemble Java and C# interfaces. Like an interface, a module allows classes that resemble each other in some way to share a common set of methods. The difference is that while an interface is completely abstract—no implementation included—a module comes complete with an implementation.

Exceptions

Most languages these days have a facility for dealing with the computing misfortunes that sometimes befall even respectable code. Ruby is no exception. When something bad happens to a good program, the Ruby interpreter will stop processing and raise an exception. The exception will bubble up the call stack until Ruby comes across code that will handle the exception or runs off the top of the call stack. In the later case Ruby will terminate your program. You can catch exceptions with a begin/rescue statement:


   begin
     quotient = 1 / 0   # Boom!
   rescue
     puts('Something bad happened')
   end


Ruby will catch any exception that might arise between the begin and rescue statements and will immediately transfer control to the code after the rescue statement if an exception is thrown. You can specify the errors that you will catch in greater detail by supplying a list of exception classes in the rescue statement:


   begin
     quotient = 1 / 0   # Boom!
   rescue ZeroDivisionError
     puts('You tried to divide by zero')
   end


If you happen to find yourself in the role of trouble source instead of trouble sink, you can raise your own exception with raise:


   if denominator == 0
     raise ZeroDivisionError
   end
   return numerator / denominator


Ruby provides a number of nice shortcuts for raising exceptions. If your raise statement calls out an exception class—as we did in the preceding example—Ruby will conveniently create a new instance of that class and use the instance as the exception. Conversely, if you supply raise with a string, Ruby will instantiate a RuntimeException and use the string as the message embedded in that exception:


   irb(main):001:0> raise 'You did it wrong'
   RuntimeError: You did it wrong


Threads

Like many recent languages, Ruby has its own built-in threading facility. Threads allow your program to do several things at once.[12] Creating new threads in Ruby is quite easy: The Thread constructor takes a block, which becomes the body of the thread. The thread starts executing the second you create it and continues executing until the block finishes. Here, for example, are a couple of threads that compute the sum and products of the first ten integers:


   thread1 = Thread.new do
     sum=0
     1.upto(10) {|x| sum = sum + x}
     puts("The sum of the first 10 integers is #{sum}")
   end
  
   thread2 = Thread.new do
     product=1
     1.upto(10) {|x| product = product * x}
     puts("The product of the first 10 integers is #{product}")
   end
  
   thread1.join
   thread2.join


You can wait for your thread to finish by using the join method:


   thread1.join
   thread2.join


While there is great power in multithreaded code, there is also a lot of danger. A great way to introduce hard-to-find bugs into your programs is to allow two or more threads to modify the same data structure at the same time. A good way to avoid this and other race conditions and make your code thread safe is to use the Monitor class:


   @monitor = Monitor.new
  
   @monitor.synchronize do
     # Only one thread at a time here...
   end


Managing Separate Source Files

One nice thing about dealing with programming examples is that they tend to be short—short enough that they can easily be stuffed into a single source file. Sadly, at some point most real applications outgrow that first file. The logical response is to break up your system into multiple files, each containing a manageable chuck of code. Of course, once you have broken your code up into those manageable chunks, you need to deal with the issue of loading all of those files. Different languages deal with this issue differently. Java, for example, has an elaborate system for loading classes automatically as a program needs them.

Ruby approaches this problem a bit differently. Ruby programs must explicitly load the classes on which they depend. For example, if your BankAccount class lives in a file called account.rb and you want to use it in your Portfolio class, which resides in portfolio.rb, you somehow need to ensure that BankAccount is actually loaded before Portfolio starts to use it. You can accomplish this with the require statement:


   require 'account.rb'
  
   class Portfolio
     # Uses BankAccount
   end


The require statement will load the contents of the file into the Ruby interpreter. The require statement is fairly smart: It will automatically add the .rb suffix. Thus, for the code above, most Ruby programmers would simply say

   require 'account'

The require statement also remembers whether a file has already been loaded and will not load the same file twice, so you do not have to worry about requiring the same file multiple times. Because require is so smart about what it has already loaded, programmers commonly require in everything they need at the top of each Ruby file—no need to fret about which classes have already been loaded by some other file.

All of this applies not just to the files that you produce, but also to the files included with the Ruby standard library. For example, if you need to parse some URLs, you can simply require in the URI class that comes with Ruby:


   require 'uri'
   yahoo = URI.parse('http://www.yahoo.com')


A final twist on the whole require saga relates to RubyGems. RubyGems is a software packaging system that lets coders release Ruby libraries and applications in convenient, easy-to-install bundles. If you want to use a library from a gem—perhaps from a gem called runt[13]—you need to require in the RubyGems support first:


   require 'rubygems'
   require 'runt'


Wrapping Up

From hello_world to modules and require, this chapter has been a whirlwind tour of Ruby. Fortunately, many of the Ruby basics—for example, the numbers, strings, and variables—are fairly commonplace. The quirks of the language, such as the not-quite-constant constants and the fact that zero is true, are not terribly over-whelming. Even so, we can begin to see, peeking out from the subbasement foundation of the language, some of the things that make Ruby such a joy. The syntax is terse but not cryptic. Everything that lives inside a program—everything from the string 'abc' to the number 42 to arrays—is an object.

As we go through the design patterns in the chapters that follow, we shall see how Ruby enables us to say some really powerful things, clearly and concisely.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.90