2nd Edition
YOUR FREE GUIDE TO PROGRAMMING RUBY FROM SAPPHIRESTEEL SOFTWARE WWW.SAPPHIRESTEEL.COM
The Little Book Of Ruby :: :: www.sapphiresteel.com :: page 2
The Little Book Of Ruby
Copyright © 2008 Dark Neon Ltd. All rights reserved.
written by
Huw Collingbourne
Download source code from: http://www.sapphiresteel.com/The-Little-Book-Of-Ruby
You may freely copy and distribute this eBook as long as you do not modify the text or remove this copyright notice. You must not make any charge for this eBook.
The Little Book of Ruby is produced in association with SapphireSteel Software, makers of the Ruby In Steel IDE for Visual Studio (www.sapphiresteel.com).
(First edition: June 2006)
Second edition: March 2008
The Little Book Of Ruby :: :: www.sapphiresteel.com :: page 3 Table of Contents
Welcome To The Little Book Of Ruby…………………………………………………………………………5 Learn Ruby In Ten Chapters< .......................................................................................... 5 What Is Ruby? ...................................................................................................................... 5 What Is Rails? ....................................................................................................................... 5 Installing And Using Ruby With Ruby In Steel .............................................................. 6 Installing Ruby Yourself ..................................................................................................... 6 Get The Source Code Of The Sample Programs ............................................................. 6 Running Ruby Programs .................................................................................................... 7 How To Use This Book ....................................................................................................... 7 Making Sense Of The Text.................................................................................................. 7
Chapter One ..............................................................................................................................9 Strings and Embedded Evaluation ................................................................................. 12 Methods .............................................................................................................................. 13 Numbers ............................................................................................................................. 15 Testing a Condition: if < then......................................................................................... 16
Chapter Two............................................................................................................................18 Instances and Instance Variables..................................................................................... 19 Constructors – new and initialize.................................................................................... 22 Inspecting Objects.............................................................................................................. 24
Chapter Three..........................................................................................................................26 Superclasses and Subclasses ............................................................................................ 29 Chapter Four ...........................................................................................................................31 Accessor Methods.............................................................................................................. 31 Attribute Readers and Writers......................................................................................... 33 Attributes Create Variables .............................................................................................. 35 Calling Methods of a Superclass ..................................................................................... 38 Class Variables ................................................................................................................... 39 Chapter Five ............................................................................................................................41 Using Arrays ...................................................................................................................... 41 Creating Arrays.................................................................................................................. 42 Multi-Dimensional Arrays ............................................................................................... 44 Iterating Over Arrays........................................................................................................ 46 Indexing Into Arrays ......................................................................................................... 47
The Little Book Of Ruby :: :: www.sapphiresteel.com :: page 4
Chapter Six ..............................................................................................................................49 Creating Hashes................................................................................................................. 49 Indexing Into A Hash........................................................................................................ 51 Hash Operations ................................................................................................................ 52
Chapter Seven .........................................................................................................................54 For Loops ............................................................................................................................ 54 Blocks................................................................................................................................... 58 While Loops........................................................................................................................ 59 While Modifiers ................................................................................................................. 60 Until Loops ......................................................................................................................... 63
Chapter Eight ..........................................................................................................................65 If..Then..Else ....................................................................................................................... 66 And..Or..Not....................................................................................................................... 68 If..Elsif.................................................................................................................................. 69 Unless .................................................................................................................................. 71 If and Unless Modifiers..................................................................................................... 71 Case Statements ................................................................................................................. 73
Chapter Nine ...........................................................................................................................76 A Module Is Like A Class<............................................................................................. 76 Module Methods................................................................................................................ 77 Modules as Namespaces................................................................................................... 78 Module ‘Instance Methods’.............................................................................................. 80 Included Modules or ‘Mixins’.......................................................................................... 80 Including Modules From Files......................................................................................... 82 Pre-Defined Modules ........................................................................................................ 83
Chapter Ten.............................................................................................................................84 IronRuby and JRuby.......................................................................................................... 84 Saving Data......................................................................................................................... 85 YAML .................................................................................................................................. 85 Files...................................................................................................................................... 86 Moving On.......................................................................................................................... 87
The Little Book Of Ruby :: Welcome To The Little Book Of Ruby :: www.sapphiresteel.com :: page 5 WELCOME TO THE LITTLE BOOK OF RUBY
Learn Ruby In Ten Chapters...
Chapter One :
Chapter Two:
Chapter Three:
Chapter Four:
Chapter Five:
Chapter Six:
Chapter Seven:
Chapter Eight:
Chapter Nine:
Chapter Ten:
What Is Ruby?
Strings and Methods
Classes and Objects
Class Hierarchies
Accessors, Attributes, Class Variables Arrays
Hashes
Loops and Iterators Conditional Statements Modules and Mixins Saving Files, Moving On<
Ruby is a cross-platform interpreted language which has many features in common with other ‘scripting’ languages such as Perl and Python. However, its version of object orientation is more thorough than those languages and, in many respects, it has more in common with the great-granddaddy of ‘pure’ OOP languages, Smalltalk. The Ruby language was created by Yukihiro Matsumoto (commonly known as ‘Matz’) and it was first released in 1995.
What Is Rails?
Currently much of the excitement surrounding Ruby can be attributed to a web development framework called Rails – popularly known as ‘Ruby On Rails’. While Rails is an impressive framework, it is not the be-all and end-all of Ruby. Indeed, if you decide to leap right into Rails development without
The Little Book Of Ruby :: Welcome To The Little Book Of Ruby :: www.sapphiresteel.com :: page 6
first mastering Ruby, you may find that you end up with an application that you don’t even understand. While the Little Book of Ruby won’t cover the special features of Rails, it will give you the grounding you need to understand Rails code and write your own Rails applications.
Installing And Using Ruby With Ruby In Steel
Ruby In Steel is a Windows-based IDE which comes with an all-in-one installer to install Ruby, Visual Studio, Ruby In Steel and various other optional packages including Rails. Be sure to read the installation guide provided with the software: http://www.sapphiresteel.com.
Installing Ruby Yourself
If you are using some other IDE or editor, you will need to download the latest version of Ruby from www.ruby-lang.org. Be sure to download the binaries (not merely the source code).
Get The Source Code Of The Sample Programs
All the programs in every chapter in this book are available for download as a Zip archive from http://www.sapphiresteel.com/The-Little-Book-Of-Ruby. When you unzip the programs you will find that they are grouped into a set of directories – one for each chapter. If you are using Ruby In Steel, you will be able to load all the programs as a single solution, with the programs for each chapter arranged on the branches of a tree in the Project Manager.
The Little Book Of Ruby :: Welcome To The Little Book Of Ruby :: www.sapphiresteel.com :: page 7 Running Ruby Programs
It is often useful to keep a Command window open in the source directory containing your Ruby program files. Assuming that the Ruby interpreter is correctly pathed on your system, you will then be able to run programs by entering ruby
ruby 1helloworld.rb
If you are using Ruby In Steel you can run the programs in the interactive console by pressing CTRL+F5 or (in some editions) you may run them in the debugger by pressing F5.
How To Use This Book
This book is a step-by-step tutorial to programming in Ruby and you can follow it chapter by chapter, reading the text and running the sample programs. On the other hand, if you prefer to ‘dip in’, you may want to try out some of the programs in whichever order takes your fancy; then refer back to the text for explanations. There are no monolithic applications in this book – just small, self-contained sample programs – so it’s easy to skip from chapter to chapter if you wish<
Making Sense Of The Text
In The Little Book Of Ruby, any Ruby source code is written like this:
def saysomething
puts( "Hello" ) end
The Little Book Of Ruby :: Welcome To The Little Book Of Ruby :: www.sapphiresteel.com :: page 8 When there is a sample program to accompany the code, the program name is
shown in a little box like this:
helloname.rb
Explanatory notes (which generally provide some hints or give a more in- depth explanation of some point mentioned in the text) are shown in a shaded box like this:
This is an explanatory note. You can skip it if you like – but if you do so, you may miss something of interest displays: “MyClass”
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 19
To make MyClass a bit more useful, I need to give it a method or two. In this example (which was mentioned briefly in the last chapter), I’ve added a method called saysomething:
class MyClass
def saysomething
puts( “Hello” ) end
end
Now, when I create a MyClass object, I can call this method in order to get that object to say ‚Hello‛:
ob = MyClass.new ob.saysomething
Instances and Instance Variables
Let’s create some more useful objects. No home (or computer program) should be without a dog. So let’s make ourselves a Dog class:
class Dog
def set_name( aName )
@myname = aName end
end
Note that the class definition begins with the keyword class (all lower case) and is followed by the name of the class itself, which must begin with an uppercase letter. My Dog class contains a single method, set_name. This takes
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 20
an incoming argument, aName. The body of the method assigns the value of
aName to a variable called @myname.
Variables beginning with the @ character are ‘instance variables’ – that means that they belong to individuals objects – or ‘instances’ of the class. It is not necessary to pre-declare variables.
I can create instances of the Dog class (that is, ‘dog objects’) by calling the new method. Here I am creating two dog objects (remember that class names begin uppercase letters; object names begin with lowercase letters):
mydog = Dog.new yourdog = Dog.new
At the moment, these two dogs have no names. So the next thing I do is call the set_name method to give them names:
mydog.set_name( ‘Fido’ ) yourdog.set_name( ‘Bonzo’ )
Having given names to the dogs, I need to have some way to find out their names later on. Each dog needs to know its own name, so let’s give it a get_name method:
def get_name return @myname
end
The return keyword here is optional. Ruby methods will always return the last expression evaluated. For the sake of clarity (and to avoid unexpected results from methods of more complexity than this one!) we shall make a habit
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 21
of explicitly returning any values which we plan to use. Finally, let’s give the
dog some behaviour by asking it to talk. Here is the finished class definition:
dogs_and_cats.rb
class Dog
def set_name( aName )
@myname = aName end
def get_name return @myname
end
def talk
return ‘woof!’
end end
Now, we can create a dog, name it, display its name and ask it to talk like this:
mydog = Dog.new mydog.set_name( ‘Fido’ ) puts(mydog.get_name) puts(mydog.talk)
For the sake of variety – and to show that I am not biased against our feline friends – I have also added a Cat class in my program, dogs_and_cats.rb. The Cat class is similar to the Dog class apart from the fact that its talk method, naturally enough, returns a miaow instead of a woof.
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 22
This program contains an error. The object named someotherdog never has a value assigned to its @name variable. Fortunately, Ruby doesn’t blow up when we try to display this dog’s name. Instead it just prints ‘nil’. We’ll shortly look at a simple way of making sure that errors like this don’t happen again<
Constructors – new and initialize
treasure.rb
For now, let’s take a look at another example of a user-defined class. Load up treasure.rb. This is an adventure game in the making. It contains two classes, Thing and Treasure. The Thing class is very similar to the Dog class from the last program – well, apart from the fact that it doesn’t woof, that is.
The Treasure class has a few interesting extras, however. First of all, it hasn’t got get_name and set_name methods. Instead, it contains a method named initialize which takes two arguments whose values are assigned to the @name and @description variables:
def initialize( aName, aDescription ) @name = aName @description = aDescription
end
When a class contains a method named initialize this is automatically called when an object is created using the new method. It is a good idea to use an initialize method to set the values of an object’s instance variables. This has two clear benefits over setting each instance variable using methods such set_name. First of all, a complex class may contain numerous instance variables and you can set the values of all of them with the single initialize
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 23
method rather than with many separate ‘set’ methods; secondly, if the variables are all automatically initialised at the time of object creation, you will never end up with an ‘empty’ variable (like the nil value returned when we tried to display the name of someotherdog in the previous program).
Note: The new method creates an object so it can be thought of as the object’s ‘constructor’. However, you should not normally implement your own version of the new method (this is possible but it is generally not advisable). Instead, when you want to perform any ‘setup’ actions – such as assigning values to an object’s internal variables - you should do so in a method named initialize. Ruby executes the initialize method immediately after a new object is created.
Finally, I have created a method called to_s which is intended to return a string representation of a Treasure object. The method name, to_s, is not arbitrary. The same method name is used throughout the standard Ruby class hierarchy. In fact, the to_s method is defined for the Object class itself which is the ultimate ancestor of all other classes in Ruby. By redefining the to_s method, I have added new behaviour which is more appropriate to the Treasure class than the default method. In other words, I have ‘overridden’ its to_s method.
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 24 Inspecting Objects
Incidentally, notice too that I have ‘looked inside’ the Treasure object, t1, using the inspect method:
t1.inspect
The inspect method is defined for all Ruby objects. It returns a string containing a human-readable representation of the object. In the present case, it displays something like this:
#
This begins with the class name, Treasure; this is followed by a number, which may be different from the one shown above – this is Ruby’s internal identification code for this particular object; then there are the names and values of the object’s variables.
p.rb
to_s.rb
To see how to_s can be used with a variety of objects and to test how a Treasure object would be converted to a string in the absence of an overridden to_s method, try out the to_s.rb program.
As you will see, classes such as Class, Object, String and Treasure, simply return their names when the to_s method is called. An object, such as the
Ruby provides the p method as a shortcut to inspect and display objects: p( anobject )
The Little Book Of Ruby :: Chapter Two :: www.sapphiresteel.com :: page 25
Treasure object, t, returns its identifier – which is the same identifier returned
by the inspect method.
Looking over my treasure.rb program I can’t help thinking that its code is a bit repetitive. After all, why have a Thing class which contains a name and a Treasure class which also contains a name (the @name instance variable), each of which are coded independently? It would make more sense to regard a Treasure as a ‘type of Thing’. If I were to develop this program into a complete adventure game, other objects such as Rooms and Weapons might be yet other ‘types of Thing’. It is clearly time to start working on a proper class hierarchy. That’s what we shall do in the next lesson<
The Little Book Of Ruby :: Chapter Three :: www.sapphiresteel.com :: page 26 Chapter Three
CLASS HIERARCHIES...
We ended the last lesson by creating two new classes: a Thing and a Treasure . In spite of the fact that these two classes shared some features (notably both had a ‘name’), there was no connection between them. Now, these two classes are so trivial that this tiny bit of repetition doesn’t really matter much. However, when you start writing real programs of some complexity, your classes will frequently contain numerous variables and methods; and you really don’t want to keep recoding the same old stuff over and over again.
It makes sense to create a class hierarchy in which a class which is a ‘special type’ of some other class simply ‘inherits’ the features of that other class. In our simple adventure game, for instance, a Treasure is a special type of Thing so the Treasure class should inherit the features of the Thing class.
Class Hierarchies – Ancestors and Descendants: In this book, I often talk about ‘descendant’ classes ‘inheriting’ features from their ‘ancestor’ classes. These terms deliberately suggest a kind a family relationship between ‘related’ classes. In Ruby, each class only has one parent. A class may, however, descend from a long and distinguished family tree with many generations of grandparents, great-grandparents and so on<
The behaviour of Things in general will be coded in the Thing class itself. The Treasure class will automatically ‘inherit’ all the features of the Thing class, so we won’t need to code them all over again. It will then add some additional features, specific to Treasures.
As a general rule, when creating a class hierarchy, the classes with the most generalised behaviour are higher up the hierarchy than classes with more
The Little Book Of Ruby :: Chapter Three :: www.sapphiresteel.com :: page 27
specialist behaviour. So a Thing class with just a name and a description, would be the ancestor of a Treasure class which has a name, a description and, additionally, a value; the Thing class might also be the ancestor of some other specialist class such as a Room which has a name, a description and also exits – and so on<
One Parent, Many Children...
The diagram above shows a Thing class which has a name and a description (in a Ruby program, these might be internal variables such as @name and @description plus some methods to access them). The Treasure and Room classes both descend from the Thing class so they automatically ‘inherit’ a name and a description. The Treasure class adds one new item: value – so it now has name, description and value; The Room class adds exits – so it has name, description and exits.
The Little Book Of Ruby :: Chapter Three :: www.sapphiresteel.com :: page 28
adventure1.rb
Let’s see how to create a descendant class in Ruby. Load up the adventure1.rb program. This starts simply enough with the definition of a Thing class which has two instance variables, @name and @description. These variables are assigned values in the initialize method when a new Thing object is created. Instance variables generally cannot (and should not) be directly accessed from the world outside the class itself due the principle of encapsulation.
‚Encapsulation‛ is a term that refers to the ‘modularity’ of an object. Put simply, it means that only the object itself can mess around with its own internal state. The outside world cannot. The benefit of this is that the programmer is able to change the implementation of methods without having to worry that some external code elsewhere in the program relies upon some specific detail of the previous implementation.
In order to obtain the value of each variable in a Thing object we need a get accessor method such as get_name; in order to assign a new value we need a set accessor method such as set_name:
def get_name
return @name end
def set_name( aName ) @name = aName
end
The Little Book Of Ruby :: Chapter Three :: www.sapphiresteel.com :: page 29 Superclasses and Subclasses
Now look at the Treasure class. Notice how this is declared:
class Treasure < Thing
The angle bracket, < , indicates that Treasure is a ‘subclass’, or descendant, of Thing and therefore it inherits the data (variables) and behaviour (methods) from the Thing class. Since the get_name, set_name, get_description and set_description methods already exist in the ancestor class (Thing) these don’t need to be re-coded in the descendant class (Treasure).
The Treasure class has one additional piece of data, its value (@value) and I have written get and set accessors for this. When a new Treasure object is created, its initialize method is automatically called. A Treasure object has three variables to initialize (@name, @description and @value), so its initialize method takes three arguments:
def initialize( aName, aDescription, aValue )
The first two arguments are passed, using the super keyword, to the initialize method of the superclass (Thing) so that the Thing class’s initialize method can deal with them:
super( aName, aDescription )
When used inside a method, the super keyword calls a method with the same name in the ancestor or ‘super’ class.
The Little Book Of Ruby :: Chapter Three :: www.sapphiresteel.com :: page 30
The current method in the Treasure class is called initialize so when code inside this method passes the two arguments ( aName, aDescription ) to super it is actually passing them to the initialize method of its superclass, Thing.
If the super keyword is used on its own, without any arguments being specified, all the arguments sent to the current method are passed to the ancestor method.
The Little Book Of Ruby :: Chapter Four :: www.sapphiresteel.com :: page 31
Chapter Four
ACCESSORS, ATTRIBUTES AND CLASS VARIABLES...
Now, getting back to the little adventure game work I was programming earlier on< I still don’t like the fact that the classes are full of repetitive code due to all those get and set accessors. Let me see what I can do to remedy that.
Accessor Methods
Instead of accessing the value of the @description instance variable with two different methods, get_description and set_description, like this<
puts( t1.get_description ) t1.set_description( “Some description” )
end
When more than one variable is supplied, these are passed to the code inside the for..end block just as you would pass arguments to a method. Here, for example, you can think of (a,b,c,d) as four arguments which are initialised, at each turn through the for loop, by the four values from a row of multiarr:
for (a,b,c,d) in multiarr
print("a=#{a}, b=#{b}, c=#{c}, d=#{d}\n" )
end
The Little Book Of Ruby :: Chapter Five :: www.sapphiresteel.com :: page 47 Indexing Into Arrays
You can index from the end of an array using minus figures, where -1 is the index of the last element; and you can also use ranges (values between a start index and an end index separated by two dots):
array_index.rb
arr = ['h','e','l','l','o',' ','w','o','r','l','d']
print( arr[0,5] ) print( arr[-5,5 ] ) print( arr[0..4] ) print( arr[-5..-1] )
#=> „hello‟ #=> „world‟ #=> „hello‟ #=> „world‟
Notice that, as with strings, when provided with two integers in order to return a number of contiguous items from an array, the first integer is the start index while the second is a count of the number of items (not an index):
arr[0,5] # returns 5 chars - ["h", "e", "l", "l", "o"]
You can also make assignments by indexing into an array. Here, for example, I first create an empty array then put items into indexes 0, 1 and 3. The ‘empty’ slot at number 2 will be filled with a nil value:
The Little Book Of Ruby :: Chapter Five :: www.sapphiresteel.com :: page 48
array_assign.rb
arr = []
arr[0] = [0]
arr[1] = ["one"]
arr[3] = ["a", "b", "c"]
# arr now contains:
# [[0], ["one"], nil, ["a", "b", "c"]]
Once again, you can use start-end indexes, ranges and negative index values:
arr2 = ['h','e','l','l','o',' ','w','o','r','l','d']
arr2[0] = 'H'
arr2[2,2] = 'L', 'L' arr2[4..6] = 'O','-','W' arr2[-4,4] = 'a','l','d','o'
# arr2 now contains:
# ["H", "e", "L", "L", "O", "-", "W", "a", "l", "d", "o"]
The Little Book Of Ruby :: Chapter Six :: www.sapphiresteel.com :: page 49 Chapter Six
HASHES...
While arrays provide a good way of indexing a collection of items by number, there may be times when it would be more convenient to index them in some other way. If, for example, you were creating a collection of recipes, it would be more meaningful to have each recipe indexed by name such as ‚Rich Chocolate Cake‛ and ‚Coq au Vin‛ rather than by numbers: 23, 87 and so on.
Ruby has a class that lets you do just that. It’s called a Hash. This is the equivalent of what some other languages call a ‘Dictionary’. Just like a real dictionary, the entries are indexed by some unique key (in a dictionary, this would be a word) and a value (in a dictionary, this would be the definition of the word).
Creating Hashes
You can create a hash by creating a new instance of the Hash class:
h1 = Hash.new
h2 = Hash.new("Some kind of ring")
hash1.rb
Both the examples above create an empty Hash. A Hash object always has a default value – that is, a value that is returned when no specific value is found at a given index. In these examples, h2 is initialized with the default value, ‚Some kind of ring‛; h1 is not initialized with a value so its default value will be nil.
The Little Book Of Ruby :: Chapter Six :: www.sapphiresteel.com :: page 50
Having created a Hash object, you can add items to it using an array-like syntax – that is, by placing the index in square brackets and using = to assign a value.
The obvious difference here being that, with an array, the index (the ‘key’) must be an integer; with a Hash, it can be any unique data item:
h2['treasure1'] = 'Silver ring' h2['treasure2'] = 'Gold ring' h2['treasure3'] = 'Ruby ring' h2['treasure4'] = 'Sapphire ring'
Often, the key may be a number or, as in the code above, a string. In principle, however, a key can be any type of object. Given some class, X, the following assignment is perfectly legal:
x1 = X.new('my Xobject') h2[x1] = 'Diamond ring'
There is a shorthand way of creating Hashes and initializing them with key- value pairs. Just add a key followed by => and its associated value; each key- value pair should be separated by a comma and the whole lot placed inside a pair of curly brackets:
h1 = { 'room1'=>'The Treasure Room', 'room2'=>'The Throne Room', 'loc1'=>'A Forest Glade', 'loc2'=>'A Mountain Stream' }
The Little Book Of Ruby :: Chapter Six :: www.sapphiresteel.com :: page 51
Unique Keys?
Take care when assigning keys to Hashes. If you use the same key twice in a Hash, you will end up over-writing the original value. This is just like assigning a value twice to the same index in an array. Consider this example:
h2['treasure1'] = 'Silver ring' h2['treasure2'] = 'Gold ring' h2['treasure3'] = 'Ruby ring' h2['treasure1'] = 'Sapphire ring'
Here the key ‘treasure1’ has been used twice. As a consequence, the original value, ‘Silver ring’ has been replaced by ‘Sapphire ring’, resulting in this Hash:
{"treasure1"=>"Sapphire ring", "treasure2"=>"Gold ring", "treasure3"=>"Ruby ring"}
Indexing Into A Hash
To access a value, place its key between square brackets:
puts(h1['room2']) #=> „The Throne Room‟
If you specify a key that does not exist, the default value is returned. Recall that we have not specified a default value for h1 but we have for h2:
p(h1['unknown_room']) #=> nil p(h2['unknown_treasure']) #=> 'Some kind of ring'
The Little Book Of Ruby :: Chapter Six :: www.sapphiresteel.com :: page 52
Use the default method to get the default value and the default= method to
set it (see Chapter 4 for more information on get and set methods): p(h1.default)
h1.default = 'A mysterious place'
Hash Operations
hash2.rb
The keys and values methods of Hash each return an array so you can use various Array methods to manipulate them. Here are a few simple examples (note, the data shown in comments beginning #=> show the values returned when each piece of code is run) :
h1 = {'key1'=>'val1', 'key2'=>'val2', 'key3'=>'val3', 'key4'=>'val4'} h2 = {'key1'=>'val1', 'KEY_TWO'=>'val2', 'key3'=>'VALUE_3', 'key4'=>'val4'}
p( h1.keys & h2.keys )
#=> ["key1", "key3", "key4"]
p( h1.values & h2.values ) #=> ["val1", "val2", "val4"]
# set intersection (keys) # set intersection (values) # concatenation
p( h1.keys+h2.keys )
#=> [ "key1", "key2", "key3", "key4", "key1", "key3", "key4", "KEY_TWO"]
p( h1.values-h2.values ) # difference #=> ["val3"]
The Little Book Of Ruby :: Chapter Six :: www.sapphiresteel.com :: page 53
p( (h1.keys << h2.keys) ) # append
#=> ["key1", "key2", "key3", "key4", ["key1", "key3", "key4", "KEY_TWO"] ]
p( (h1.keys << h2.keys).flatten.reverse ) # „un-nest‟ arrays and reverse #=> ["KEY_TWO", "key4", "key3", "key1", "key4", "key3", "key2", "key1"]
Be careful to note the difference between concatenating using + to add the values from the second array to the first array and appending using << to add the second array itself as the final element of the first array:
a =[1,2,3]
b =[4,5,6]
c = a + b #=> c=[1, 2, 3, 4, 5, 6] a=[1, 2, 3] a << b #=> a=[1, 2, 3, [4, 5, 6]]
In addition << modifies the first (the ‘receiver’) array whereas + returns a new array but leaves the receiver array unchanged. If, after appending an array with << you decide that you’d like to add the elements from the appended array to the receiver array rather than have the appended array itself ‘nested’ inside the receiver, you can do this using the flatten method:
a=[1, 2, 3, [4, 5, 6]]
a.flatten #=> [1, 2, 3, 4, 5, 6]
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 54 Chapter Seven
LOOPS AND ITERATORS...
Much of programming is concerned with repetition. You may want a program to beep 10 times, read lines from a file just so long as there are more lines to read or display a warning until the user presses a key. Ruby provides a number of ways of performing this kind of repetition.
For Loops
In many programming languages, when you want to run a bit of code a certain number of times you can just put it inside a for loop. In most languages, you have to give a for loop a variable initialized with a starting value which is incremented by 1 on each turn through the loop until it meets some specific ending value. When the ending value is met, the for loop stops running. Here’s a version of this traditional type of for loop written in Pascal:
(* This is Pascal code, not Ruby! *) for i := 1 to 3 do
writeln( i );
for_loop.rb
You may recall from Chapter Five (arrays) that Ruby’s for loop doesn’t work like this at all! Instead of giving it a starting and ending value, we give the for loop a list of items and it iterates over them, one by one, assigning each value in turn to a loop variable until it gets to the end of the list.
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 55
For example, here is a for loop that iterates over the items in an array,
displaying each in turn:
# This is Ruby code... for i in [1,2,3] do
puts( i ) end
The for loop is more like the ‘for each’ iterator provided by some other programming languages. Indeed, the author of Ruby describes for as ‚syntax sugar‛ for the each method which is implemented by Ruby's collection types such as Arrays, Sets, Hashes and Strings (a String being, in effect, a collection of characters).
For the sake of comparison, this is the for loop shown above rewritten using the each method:
each_loop.rb
[1,2,3].each do |i| puts( i )
end
As you can see, there isn’t really all that much difference.
To convert the for loop to an each iterator, all I’ve had to do is delete for and in and append .each to the array. Then I’ve put the iterator variable, i, between a pair of upright bars after do.
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 56
Compare these other examples to see just how similar for loops are to each
iterators:
# --- Example 1 ---
# i) for
for s in ['one','two','three'] do
puts( s ) end
# ii) each ['one','two','three'].each do |s|
puts( s ) end
# --- Example 2 ---
# i) for
for x in [1, "two", [3,4,5] ] do puts( x ) end
# ii) each
[1, "two", [3,4,5] ].each do |x| puts( x ) end
for_each.rb
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 57
Note, incidentally, that the do keyword is optional in a for loop that spans
multiple lines but it is obligatory when it is written on a single line:
# Here the „do‟ keyword can be omitted for s in ['one','two','three']
puts( s ) end
# But here it is required
for s in ['one','two','three'] do puts( s ) end
How to write a ‘normal’ for loop...
for_to.rb
If you miss the traditional type of for loop, you can always ‘fake’ it in Ruby by using a for loop to iterate over the values in a range. For example, this is how to use a for loop variable to count up from 1 to 10, displaying its value at each turn through the loop:
for i in (1..10) do puts( i )
end
Which can be rewritten using each:
(1..10).each do |i| puts(i)
end
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 58
Note, incidentally, that a range expression such as 1..3 must be enclosed between round brackets when used with the each method, otherwise Ruby assumes that you are attempting to use each as a method of the final integer (a FixNum) rather than of the entire expression (a Range). The brackets are optional when a range is used in a for loop.
When iterating over items using each the block of code between do and end is called (predictably, perhaps?) an ‘iterator block’.
Block Parameters: In Ruby any variables declared between upright bars at the top of a block are called ‘block parameters’. In a way, a block works like a function and the block parameters work like a function’s argument list. The each method runs the code inside the block and passes to it the arguments supplied by a collection (for example, an array).
Blocks
Ruby has an alternative syntax for delimiting blocks. You may use do..end, like this...
block_syntax.rb
# do..end [[1,2,3],[3,4,5],[6,7,8]].each do
|a,b,c|
puts( "#{a}, #{b}, #{c}" )
end
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 59 Or you can use curly braces {..} like this:
# curly braces {..} [[1,2,3],[3,4,5],[6,7,8]].each{
|a,b,c|
puts( "#{a}, #{b}, #{c}" )
}
No matter which block delimiters you use, you must ensure that the opening delimiter, ‘{‘ or ‘do’, is placed on the same line as the each method. Inserting a line break between each and the opening block delimiter is a syntax error.
While Loops
Ruby has a few other loop constructs too. This is how to do a while loop:
while tired sleep
end
Or, to put it another way:
sleep while tired
Even though the syntax of these two examples is different they perform the same function. In the first example, the code between while and end (here a call to a method named sleep) executes just as long as the Boolean condition (which, in this case, is the value returned by a method called tired) evaluates to true.
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 60
As in for loops the keyword do may optionally be placed between the test condition and the code to be executed when these appear on separate lines; the do keyword is obligatory when the test condition and the code to be executed appear on the same line.
While Modifiers
In the second version of the loop (sleep while tired), the code to be executed (sleep) precedes the test condition (while tired). This syntax is called a ‘while modifier’. When you want to execute several expressions using this syntax, you can put them between the begin and end keywords:
begin sleep
snore
end while tired
This is an example showing the various alternative syntaxes:
$hours_asleep = 0
def tired
if $hours_asleep >= 8 then
$hours_asleep = 0
return false else
$hours_asleep += 1
return true end
end
while.rb
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 61
def snore puts('snore....')
end
def sleep
puts("z" * $hours_asleep )
end
while tired do sleep end
while tired sleep
end
sleep while tired
begin sleep
snore
end while tired
# a single-line while loop # a multi-line while loop
# single-line while modifier # multi-line while modifier
The last example above (the multi-line while modifier) needs close consideration as it introduces some important new behaviour. When a block of code delimited by begin and end precedes the while test, that code always executes at least once. In the other types of while loop, the code may never execute at all if the Boolean condition initially evaluates to true.
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 62
while2.rb
Ensuring a Loop Executes At Least Once
Usually a while loops executes 0 or more times since the Boolean test is evaluated before the loop executes; if the test returns false at the outset, the code inside the loop never runs.
However, when the while test follows a block of code enclosed between begin and end, the loop executes 1 or more times as the Boolean expression is evaluated after the code inside the loop executes.
To appreciate the differences in behaviour of these two types of while loop, run while2.rb. These examples should help to clarify:
x = 100
# The code in this loop never runs
while (x < 100) do puts('x < 100') end
# The code in this loop never runs puts('x < 100') while (x < 100)
# But the code in loop runs once begin puts('x < 100') end while (x < 100)
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 63 Until Loops
Ruby also has an until loop which can be thought of as a ‘while not’ loop. Its syntax and options are the same as those applying to while – that is, the test condition and the code to be executed can be placed on a single line (in which case the do keyword is obligatory) or they can be placed on separate lines (in which case do is optional). There is also an until modifier which lets you put the code before the test condition; and there is the option of enclosing the code between begin and end in order to ensure that the code block is run at least once.
until.rb
Here are some simple examples of until loops: i = 10
until i == 10 do puts(i) end
until i == 10 puts(i)
i += 1 end
puts(i) until i == 10
begin puts(i)
end until i == 10
# never executes # never executes
# never executes # executes once
The Little Book Of Ruby :: Chapter Seven :: www.sapphiresteel.com :: page 64
Both while and until loops can, just like a for loop, be used to iterate over arrays and other collections. For example, this is how to iterate over all the elements in an array:
while i < arr.length puts(arr[i])
i += 1
end
until i == arr.length puts(arr[i])
i +=1
end
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 65 Chapter Eight
CONDITIONAL STATEMENTS...
Computer programs, like Life Itself, are full of difficult decisions waiting to be made. Things like: If I stay in bed I will get more sleep, else I will have to go to work; if I go to work I will earn some money, else I will lose my job - and so on<
We’ve already performed a number of if tests in previous programs. To take a simple example, this is from the Tax calculator in chapter one:
if (subtotal < 0.0) then
subtotal = 0.0 end
In this program, the user was prompted to enter a value, subtotal, which was then used in order to calculate the tax due on it. The little test above ensures that subtotal is never a minus figure. If the user, in a fit of madness, enters a value less than 0, the if test spots this since the condition (subtotal < 0.0) evaluates to true, which causes the body of the code between the if test and the end keyword to be executed; here, this sets the value of subtotal to 0.
Equals once = or equals twice == ?
In common with many other programming languages, Ruby uses one equals sign to assign a value = and two to test a value ==.
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 66 If..Then..Else
if_else.rb
A simple if test has only one of two possible results. Either a bit of code is run or it isn’t, depending on whether the test evaluates to true or not.
Often, you will need to have more than two possible outcomes. Let’s suppose, for example, that your program needs to follow one course of action if the day is a weekday and a different course of action if it is a weekend. You can test these conditions by adding an else section after the if section, like this:
if aDay == 'Saturday' or aDay == 'Sunday' daytype = 'weekend'
else
daytype = 'weekday'
end
The if condition here is straightforward. It tests two possible conditions:
1) if the value of the variable, aDay is equal to the string ‘Saturday’ or..
2) if the value of aDay is equal to the string ‘Sunday’.
If either of those conditions is true then the next line of code executes:
daytype = 'weekend'
In all other cases, the code after else executes: daytype = 'weekday'.
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 67
if_then.rb
When an if test and the code to be executed are placed on separate lines, the then keyword is optional. When the test and the code are placed on a single line, the then keyword (or, if you prefer really terse code, a colon character) is obligatory:
if x == 1 then puts( 'ok' ) end if x == 1 : puts( 'ok' ) end
if x == 1 puts( 'ok' ) end
# with 'then' # with colon
# syntax error!
An if test isn’t restricted to evaluating just two conditions. Let’s suppose, for example, that your code needs to work out whether a certain day is a working day or a holiday. All weekdays are working days; all Saturdays are holidays but Sundays are only holidays when you are not working overtime.
This is my first attempt to write a test to evaluate all these conditions:
working_overtime = true
if aDay == 'Saturday' or aDay == 'Sunday' and not working_overtime daytype = 'holiday'
puts( "Hurrah!" ) else
daytype = 'working day' end
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 68
and_or.rb
Unfortunately, this doesn’t have quite the effect intended. Remember that Saturday is always a holiday. But this code insists that ‘Saturday’ is a working day. This is because Ruby takes the test to mean: “If the day is Saturday and I am not working overtime, or if the day is Sunday and I am not working overtime” whereas what I really meant was “If the day is Saturday; or if the day is Sunday and I am not working overtime”.
The easiest way to resolve this ambiguity is to put brackets around any code to be evaluated as a single unit, like this:
if aDay == 'Saturday' or (aDay == 'Sunday' and not working_overtime)
And..Or..Not
Incidentally, Ruby has two different syntaxes for testing Boolean (true/false) conditions.
In the above example, I’ve used the English-language style operators: and, or and not. If you prefer you could use alternative operators similar to those used in many other programming languages, namely: && (and), || (or) and ! (not).
Be careful, though, the two sets of operators aren’t completely interchangeable. For one thing, they have different precedence which means that when multiple operators are used in a single test, the parts of the test may be evaluated in different orders depending on which operators you use.
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 69 If..Elsif
There will no doubt be occasions when you will need to take multiple different actions based on several alternative conditions. One way of doing this is by evaluating one if condition followed by a series of other test conditions placed after the keyword elsif. The whole lot must then be terminated using the end keyword.
if_elsif.rb
For example, here I am repeatedly taking input from a user inside a while loop; an if condition tests if the user enters ‘q’ (I’ve used the chomp() method to remove the carriage return from the input); if ‘q’ is not entered the first elsif condition tests if the integer value of the input (input.to_i) is greater than 800; if this test fails the next elsif condition tests if it is less than or equal to 800:
while input != 'q' do
puts("Enter a number between 1 and 1000 (or 'q' to quit)") print("?- ")
input = gets().chomp()
if input == 'q'
puts( "Bye" )
elsif input.to_i > 800
puts( "That's a high rate of pay!" ) elsif input.to_i <= 800
puts( "We can afford that" ) end
end
This code has a bug. It asks for a number between 1 and 1000 but it accepts other numbers. See if you can rewrite the tests to fix this!
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 70
if_else_alt.rb
Ruby also has a short-form notation for if..then..else in which a question mark ? replaces the if..then part and a colon : acts as else<
< Test Condition > ?
For example:
x == 10 ? puts("it's 10") : puts( "it's some other number" )
When the test condition is complex (if it uses ands and ors) you should enclose it in brackets.
If the tests and code span several lines the ? must be placed on the same line as the preceding condition and the : must be placed on the same line as the code immediately following the ?.
In other words, if you put a newline before the ? or the : you will generate a syntax error. This is an example of a valid multi-line code block:
(aDay == 'Saturday' or aDay == 'Sunday') ? daytype = 'weekend' :
daytype = 'weekday'
The Little Book Of Ruby :: Chapter Eight :: www.sapphiresteel.com :: page 71 Unless
unless.rb
Ruby also can also perform unless tests, which are the opposite of if tests:
unless aDay == 'Saturday' or aDay == 'Sunday' daytype = 'weekday'
else
daytype = 'weekend'
end
Think of unless as being an alternative way of expressing ‘if not’. The
following is equivalent to the code above:
if !(aDay == 'Saturday' or aDay == 'Sunday') daytype = 'weekday'
else
daytype = 'weekend'
end
If and Unless Modifiers
You may recall the alternative syntax for while loops in Chapter 7. Instead of writing this<
while tired do sleep end