Ruby Notes

Russell Bateman
August 2013
last update:

I'm not super-serious about learning Ruby at this point, but my ability to Chef stands greatly impaired until I do learn enough of this language. These are some short notes I'm keeping separate from a Ruby training exercise I tackled. To read these is to begin to see Ruby through the eyes of this old C and now Java programmer.

These notes are very rough right now and mostly motivated by the training I followed. Most of the examples are just slapped from there onto this page without changing them to be more to the point. This said, however, they can be pasted into irb or files, then executed to see their output.

I need a place early on where I can deposit notes on this language until I become more at ease. This will help me avoiding re-looking things up. Consequently, they will be augmented and polished very dynamically over the next while.

Super quick-reference aids!

- zenspider.com: Ruby QuickRef, it's got everything!
- tutorialspoint: Simply Easy Learning: Ruby Programming, it's got everything too!
- Google.com (Ben, voyons !)

Legend

I use the following HTML/CSS constructs to frame my examples. Inside the console output should be what you'd see if you scraped the file contents or source code boxes into a file and executed them using Ruby.


  Source code  


  File contents  


  Console output  


Best practice

Here and there are mentions of best practice that I noted while completing the Ruby training exercises. Some of these are interesting, some are not and some may not appear interesting at first, but may become so with more experience. Some of this advice smells like an absence of TDD. It all seemed worth noting until more experience comes. This said, here are best practices for...


Odd notation in Ruby

Well, odd at first until you get used to it. Some of this is by convention, some is imposed by Ruby. Also, remember that Ruby is, above all, a scripting language.

Operators

@ sigil* denoting a variable holding an object/instance of a class
@@ sigil denoting a class variable
$ sigil denoting a global variable
% combined with other stuff (see below)
=> called "hashrocket", used to assign values to tuples in hashes
self sort of like this in Java
nil sole instance of NilClass
true sole instance of TrueClass
false sole instance of FalseClass
__FILE__ current source-file name
__LINE__ current source-file line
$DEBUG status of the -d command-line option
$FILENAME current input file from ARGF ARGF.filename
$LOAD_PATH load path for scripts and binary modules by load or require
$stderr current standard error output
$stdout current standard output
$stdin current standard input
$VERBOSE verbose flag, set by -v command-line option
# single evaluation: the sum of x and y is #{ x+y }
%q, %Q to initialize string arrays
%w, %W to initialize string arrays
: symbol; guaranteed unique, no need to initialize
See more stuff like this at Ruby QuickRef.

* a variable without a sigil is a local variable and scoped to the enclosing method, block or module.


Numbers and math

Operators

+ plus
- minus
/ slash
* asterisk
% percent
< less-than
> greater-than
<= less-than-equal
>= greater-than-equal

Symbols

(Not sure where to put this.)

A symbol is like a variable, but it's constant, guaranteed unique and there's no need to initialize it to be "something". Here is an exercise, using object_id (sort of like Java hashCode()):

puts "Oregon".object_id       # (string)
puts "Oregon".object_id       # (different string!)
puts :state.object_id         # symbol
puts :state.object_id         # (yup, it's the same thing)
string = "Oregon"             # here's the string "Oregon"
puts string.to_sym            # make it a symbol
puts string.to_sym.object_id  # get its object id
puts string.object_id         # and its string object id
4621300
4621120
163048
163048

Oregon
455788
16883940

When to use a string or a symbol?

When the contents of the object are important, use a string, "Oregon". If only the identity of the object is significant, use a symbol, :state.

So, a Ruby symbol is a sort of constant variable that need not be predefined because...

In fact, Ruby symbols aren't constants, instead they are "a consistent name within code".

A symbol object is created by prefixing a Ruby operator, string, variable, constant, method, class or module name with a colon.

Symbols are particularly useful in creating hash maps in which there is to be a careful distinction between keys and values. Thus, the two bits of code below have the same effect, the second and third using symbols. (The third example also uses syntax new to Ruby 1.9.)

french_hashmap = { 'dog' => "chien", 'cat' => "chat", 'ant' => "fourmie" }
puts french_hashmap[ 'dog' ]
chien
french_hashmap = { :dog => "chien", :cat => "chat", :ant => "fourmie" }
puts french_hashmap[ :cat ]
chat
french_hashmap = { dog: "chien", cat: "chat", :ant: "fourmie" }
puts french_hashmap[ :ant ]
fourmie

Reference: Ruby Symbols.
Example using hash maps: Ruby Hashes.


Arguments

args.rb:

first, second, third = ARGV

puts "The script is called: #{$0}"
puts "Your first variable is: #{first}"
puts "Your second variable is: #{second}"
puts "Your third variable is: #{third}"

$ ruby args.rb 1st 2nd 3rd
The script is called: args.rb
Your first variable is: 1st
Your second variable is: 2nd
Your third variable is: 3rd

Methods (functions)

methods-and-args.rb:

# this one is like your scripts with argv
def puts_two( *args )
  arg1, arg2 = args
  puts "arg1: #{arg1}, arg2: #{arg2}"
end

# ok, that *args is actually pointless, we can just do this
def puts_two_again( arg1, arg2 )
  puts "arg1: #{arg1}, arg2: #{arg2}"
end

# this just takes one argument
def puts_one( arg1 )
  puts "arg1: #{arg1}"
end

# this one takes no arguments
def puts_none()
  puts "I got nothin'."
end

puts_two( "Zed","Shaw" )
puts_two_again( "Zed","Shaw" )
puts_one( "First!" )
puts_none()

$ ruby methods-and-args.rb
arg1: Zed, arg2: Shaw
arg1: Zed, arg2: Shaw
arg1: First!
I got nothin'.

two-returns.rb:

def return_two_things
  return true, false
end

two_things = return_two_things
puts two_things[ 0 ]
puts two_things[ 1 ]

$ ruby methods-and-args.rb
true
false

Logical expressions

The relational operators and other elements of logic in Ruby are:


Ontological expressions, include?()

Ruby's array include? method returns true/false that some value is an element of (in) the array. Here's some playing around to illustrate that:

~/dev/ruby $ irb
irb(main):001:0> array = [ 'cat', 'dog', 'ant' ]
=> ["cat", "dog", "ant"]
irb(main):002:0> array.include? "dog"
=> true
irb(main):003:0> array = [ 1, 2, 3 ]
=> [1, 2, 3]
irb(main):004:0> array.include?( 3 )
=> true
irb(main):005:0> array.include? 2
=> true

Conditionals

Ruby conditionals
if if expression   true-part   end
if if expression   true-part   elsif expression   true-part   end
if if expression   true-part   else   false-part   end

if-statements best practice

  1. Every if-statement must have an else.
  2. If this else should never be run because it doesn't make sense, then use a die function in the else that prints out an error message and dies, just as done in the last exercise. This will find many errors.
  3. Never nest if-statements more than 2 deep and always try to do them 1 deep. This means if you put an if in an if then you should be looking to move that second if into a method.
  4. Treat if-statements like paragraphs, where each if, elsif, else grouping is like a set of sentences. Put blank lines before and after.
  5. Your boolean tests should be simple. If they are complex, move their calculations to variables earlier in your function and use a good name for the variable.

people = 20
cats = 30
dogs = 15

if people < cats
  puts "Too many cats! The world is doomed!"
end

if people > cats
  puts "Not many cats! The world is saved!"
end

if people < dogs
  puts "The world is drooled on!"
end

if people > dogs
  puts "The world is dry!"
end

dogs += 5

if people >= dogs
  puts "People are greater than or equal to dogs."
end

if people <= dogs
  puts "People are less than or equal to dogs."
end

if people == dogs
  puts "People are dogs."
end

if( people == dogs )             # added parens just to see if it works.)
  puts "My parens work."
end
Too many cats! The world is doomed!
The world is dry!
People are greater than or equal to dogs.
People are less than or equal to dogs.
People are dogs.
My parens work.

people = 30
cars = 40
buses = 15

if cars > people
  puts "We should take the cars."
elsif cars < people
  puts "We should not take the cars."
else
  puts "We can't decide."
end

if buses > cars
  puts "That's too many buses."
elsif buses < cars
  puts "Maybe we could take the buses."
else
  puts "We still can't decide."
end

if people > buses
  puts "Alright, let's just take the buses."
else
  puts "Fine, let's stay home then."
end
We should take the cars.
Maybe we could take the buses.
Alright, let's just take the buses.

Statements on one line in Ruby

It's useful in some settings, such as using irb, to keep parts of full statements or several statements on a single line. In Ruby, the semicolon is the statement separator. As you can see, these are conveniently identical:

found = false
if found
  puts "Found!"
else
  puts "Not found!"
end

irb(main):001:0> if found
irb(main):002:1> puts "Found!"
irb(main):003:1> else
irb(main):004:1* puts "Not found!"
irb(main):005:1> end
Not found!
=> nil
 
found = false
if found ; puts "Found!"; else; puts "Not found!"; end

irb(main):006:0> if found ; puts "Found!" ; else ; puts "Not found!" ; end
Not found!
=> nil

Control statements

Ruby control statements (loops)
for for variable in collection   ...   end
while while expression   ...   end

for and while loops best practice

  1. Use a while loop only to loop forever, and that means probably never. This only applies to Ruby, other languages are different.
  2. Use a for loop for all other kinds of looping, especially if there is a fixed or limited number of things to loop over.

The for loop

the_count = [ 1, 2, 3, 4, 5 ]
fruits = [ 'apples', 'oranges', 'pears', 'apricots' ]
change = [ 1, 'pennies', 2, 'dimes', 3, 'quarters' ]

# this first kind of for-loop goes through an array
for number in the_count
  puts "This is count #{number}"
end

# same as above, but using a block instead
fruits.each do |fruit|
  puts "A fruit of type: #{fruit}"
end

# also we can go through mixed arrays too
for i in change
  puts "I got #{i}"
end

# we can also build arrays, first start with an empty one
elements = []

# then use a range object to do 0 to 5 counts
for i in ( 0..5 )
  puts "Adding #{i} to the list."
  # push is a function that arrays understand
  elements.push( i )
end

# now we can puts them out too
for i in elements
  puts "Element was: #{i}"
end
This is count 1
This is count 2
This is count 3
This is count 4
This is count 5
A fruit of type: apples
A fruit of type: oranges
A fruit of type: pears
A fruit of type: apricots
I got 1
I got pennies
I got 2
I got dimes
I got 3
I got quarters
Adding 0 to the list.
Adding 1 to the list.
Adding 2 to the list.
Adding 3 to the list.
Adding 4 to the list.
Adding 5 to the list.
Element was: 0
Element was: 1
Element was: 2
Element was: 3
Element was: 4
Element was: 5

The while loop

i = 0
numbers = []

while i < 6
  puts "At the top i is #{i}"
  numbers.push( i )

  i = i + 1
  puts "Numbers now: #{numbers}"
  puts "At the bottom i is #{i}"
end

puts "The numbers: "

for num in numbers
  puts num
end
At the top i is 0
Numbers now: [0]
At the bottom i is 1
At the top i is 1
Numbers now: [0, 1]
At the bottom i is 2
At the top i is 2
Numbers now: [0, 1, 2]
At the bottom i is 3
At the top i is 3
Numbers now: [0, 1, 2, 3]
At the bottom i is 4
At the top i is 4
Numbers now: [0, 1, 2, 3, 4]
At the bottom i is 5
At the top i is 5
Numbers now: [0, 1, 2, 3, 4, 5]
At the bottom i is 6
The numbers:
0
1
2
3
4
5

Arrays

You can have arrays of anything.

There are some cool things about arrays in Ruby. Check out Array for methods. There is no lack of functionality. Ellipese are used to shorten the output below.

animals    = [ 'bear', 'tiger', 'penguin', 'zebra' ]
colors     = [ "red", "yellow", "blue", "green" ]
digits     = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
hexdigits  = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ]
hexnumbers = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f ]

puts animals
puts colors
puts digits
puts hexdigits
puts hexnumbers
bear
tiger
penguin
zebra
red
yellow
blue
green
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
...
e
f
0
1
2
3
4
...
14
15

%w( foo bar ) is a shortcut for [ "foo", "bar" ] in Ruby for writing an array of strings separated by spaces instead of commas and without the need to quote.


Hash maps

In Ruby, a hash is what is more properly termed a map or, if the notion of the underlying indexing strategy is to be included, a hash map.

Hash maps do not have order. If you print one out, it comes out in whatever order they're stored in, which from my observation is, at least at first, the order in which they're built.

This construct is what imparts the greatest amount of obfuscation to reading Ruby when coming from another language. It's the "hash rocket" that does it.

# create a mapping of state to abbreviation
states =
{
    'Oregon'     => 'OR',
    'Florida'    => 'FL',
    'California' => 'CA',
    'New York'   => 'NY',
    'Michigan'   => 'MI'
}

# create a basic set of states and some cities in them
cities =
{
    'CA' => 'San Francisco',
    'MI' => 'Detroit',
    'FL' => 'Jacksonville'
}

# add some more cities
cities['NY'] = 'New York'
cities['OR'] = 'Portland'

# puts out some cities
puts '-' * 10
puts "NY State has: ", cities[ 'NY' ]
puts "OR State has: ", cities[ 'OR' ]

# puts some states
puts '-' * 10
puts "Michigan's abbreviation is: ", states[ 'Michigan' ]
puts "Florida's abbreviation is: ", states[ 'Florida' ]

# do it by using the state then cities dictionary
puts '-' * 10
puts "Michigan has: ", cities[ states[ 'Michigan' ] ]
puts "Florida has: ", cities[ states[ 'Florida' ] ]

# puts every state abbreviation
puts '-' * 10
for state, abbrev in states
    puts "%s is abbreviated %s" % [ state, abbrev ]
end

# puts every city in state
puts '-' * 10
for abbrev, city in cities
    puts "%s has the city %s" % [ abbrev, city ]
end

# now do both at the same time
puts '-' * 10
for state, abbrev in states
    puts "%s state is abbreviated %s and has city %s" % [
        state, abbrev, cities[ abbrev ] ]
end

puts '-' * 10
# if it's not there you get nil
state = states[ 'Texas' ]

if not state
    puts "Sorry, no Texas."
end

# get a city with a default value
city = cities[ 'TX' ] || 'Does Not Exist'
puts "The city for the state 'TX' is: %s" % city
----------
NY State has:
New York
OR State has:
Portland
----------
Michigan's abbreviation is:
MI
Florida's abbreviation is:
FL
----------
Michigan has:
Detroit
Florida has:
Jacksonville
----------
Oregon is abbreviated OR
Florida is abbreviated FL
California is abbreviated CA
New York is abbreviated NY
Michigan is abbreviated MI
----------
CA has the city San Francisco
MI has the city Detroit
FL has the city Jacksonville
NY has the city New York
OR has the city Portland
----------
Oregon state is abbreviated OR and has city Portland
Florida state is abbreviated FL and has city Jacksonville
California state is abbreviated CA and has city San Francisco
New York state is abbreviated NY and has city New York
Michigan state is abbreviated MI and has city Detroit
----------
Sorry, no Texas.
The city for the state 'TX' is: Does Not Exist

Beginning in Ruby 1.9, the standard "hashrocket" syntax was made optional. The first two statements are equivalent, but the third one is not (as I understand it).

languages = { 'French' => 'fr', 'German' => 'de' }
languages = { French: 'fr', 'German': => 'de' }
languages = { "French" => 'fr', German => 'de' }

A missing key in the hash behaves as following (look for primary in the replica sets below). The expression, if replica_1[ :primary ], will not match when replica_1[ :primary ] is set to false or nil or is missing altogether (as here).

require 'pp'
replica_1 = { :hostname => "16.86.193.100", :port => 27017 }
replica_2 = { :hostname => "16.86.193.102", :port => 27019, :primary => true }

puts "replica_1=", replica_1
if replica_1[ :primary ]
  puts "replica_1[ :primary ] is set"
else
  puts "replica_1[ :primary ] is not set/true"
end

puts "replica_2=", replica_2
if replica_2[ :primary ]
  puts "replica_2[ :primary ] is set/true"
else
  puts "replica_2[ :primary ] is not set"
end
replica_1={:hostname=>"16.86.193.100", :port=>27017}
replica_1[ :primary ] is not set/true
replica_2={:hostname=>"16.86.193.102", :port=>27019, :primary=>true}
replica_2[ :primary ] is set/true

CAVEAT: It seems to me that half the time, this doesn't work in running code. It's always "true" or "non-nil" with the result that what I'm trying not to do get done anyway. After hair-pulling and abuse by a couple of members of the stackoverflow.com Ruby community, I have resolved that it's much better to set and check for something specific. Don't rely on the example above working. I advise instead something like:

require 'pp'
replica_1 = { :hostname => "16.86.193.100", :port => 27017 }
replica_2 = { :hostname => "16.86.193.102", :port => 27019, :primary => true }

puts "replica_1=", replica_1
if replica_1[ :primary ] == true
  puts "replica_1[ :primary ] is set"
else
  puts "replica_1[ :primary ] is not set/true"
end

puts "replica_2=", replica_2
if replica_2[ :primary ] == true
  puts "replica_2[ :primary ] is set/true"
else
  puts "replica_2[ :primary ] is not set"
end
replica_1={:hostname=>"16.86.193.100", :port=>27017}
replica_1[ :primary ] is not set/true
replica_2={:hostname=>"16.86.193.102", :port=>27019, :primary=>true}
replica_2[ :primary ] is set/true

Classes and objects

These are here to be waded through and compared.

class Song
    def initialize( lyrics )
        @lyrics = lyrics
    end

    def sing_me_a_song()
        for line in @lyrics
            puts line
        end
    end
end

happy_bday = Song.new( [ "Happy birthday to you",
                   "I don't want to get sued",
                   "So I'll stop right there" ] )

bulls_on_parade = Song.new( [ "They rally around the family",
                        "With pockets full of shells" ] )

happy_bday.sing_me_a_song()

bulls_on_parade.sing_me_a_song()
Happy birthday to you
I don't want to get sued
So I'll stop right there
They rally around the family
With pockets full of shells
class Parent
    def implicit()
        puts "PARENT implicit()"
    end
end

class Child < Parent
end

dad = Parent.new()
son = Child.new()

dad.implicit()
son.implicit()
PARENT implicit()
PARENT implicit()
class Parent
    def override()
        puts "PARENT override()"
    end
end

class Child < Parent
    def override()
        puts "CHILD override()"
    end
end

dad = Parent.new()
son = Child.new()

dad.override()
son.override()
PARENT override()
CHILD override()
class Parent
    def altered()
        puts "PARENT altered()"
    end
end

class Child < Parent
    def altered()
        puts "CHILD, BEFORE PARENT altered()"
        super()
        puts "CHILD, AFTER PARENT altered()"
    end

end

dad = Parent.new()
son = Child.new()

dad.altered()
son.altered()
PARENT altered()
CHILD, BEFORE PARENT altered()
PARENT altered()
CHILD, AFTER PARENT altered()
class Parent
    def override()
        puts "PARENT override()"
    end

    def implicit()
        puts "PARENT implicit()"
    end

    def altered()
        puts "PARENT altered()"
    end
end

class Child < Parent
    def override()
        puts "CHILD override()"
    end

    def altered()
        puts "CHILD, BEFORE PARENT altered()"
        super()
        puts "CHILD, AFTER PARENT altered()"
    end
end

dad = Parent.new()
son = Child.new()

dad.implicit()
son.implicit()

dad.override()
son.override()

dad.altered()
son.altered()
PARENT implicit()
PARENT implicit()
PARENT override()
CHILD override()
PARENT altered()
CHILD, BEFORE PARENT altered()
PARENT altered()
CHILD, AFTER PARENT altered()
class Other
    def override()
        puts "OTHER override()"
    end

    def implicit()
        puts "OTHER implicit()"
    end

    def altered()
        puts "OTHER altered()"
    end
end

class Child
    def initialize()
        @other = Other.new()
    end

    def implicit()
        @other.implicit()
    end

    def override()
        puts "CHILD override()"
    end

    def altered()
        puts "CHILD, BEFORE OTHER altered()"
        @other.altered()
        puts "CHILD, AFTER OTHER altered()"
    end
end

son = Child.new()

son.implicit()
son.override()
son.altered()
OTHER implicit()
CHILD override()
CHILD, BEFORE OTHER altered()
OTHER altered()
CHILD, AFTER OTHER altered()

Nil and empty

Any object can be nil or empty (or contain something that's not empty). The distinction between nil and empty is an important one.

irb(main):001:0> node = Hash.new { | h,k | h[ k ] = Hash.new( &h.default_proc ) }
=> {}
irb(main):002:0> node[ :crap ] = "fun"
=> "fun"
irb(main):003:0> puts node[ :crap ]
fun
=> nil
irb(main):004:0> puts node[ :poop ]
{}
=> nil
irb(main):005:0> thing = node[ :poop ]
=> {}
irb(main):006:0> puts thing
{}
=> nil
irb(main):007:0> if thing.nil?
irb(main):008:1> puts "thing is nil"
irb(main):009:1> else
irb(main):010:1* puts "thing isn't nil"
irb(main):011:1> end
thing isn't nil
=> nil
irb(main):012:0> thing.nil?
=> false
irb(main):013:0> thing.empty?
=> true
irb(main):014:0> if thing.empty?
irb(main):015:1> puts "thing is empty"
irb(main):016:1> else
irb(main):017:1* puts "thing isn't empty"
irb(main):018:1> end
thing is empty
=> nil
node = Hash.new { | h,k | h[ k ] = Hash.new( &h.default_proc ) }
node[ :crap ] = "fun"
puts node[ :crap ]
puts node[ :poop ]

thing = node[ :poop ]
puts thing

if thing.nil?
  puts "thing is nil"
else
  puts "thing isn't nil"
end

thing.nil?
thing.empty?

if thing.empty?
  puts "thing is empty"
else
  puts "thing isn't empty"
end

Exception handling and raising

irb(main):005:0> Integer( 3 )
=> 3
irb(main):006:0> Integer( 3.2 )
=> 3
irb(main):007:0> Integer( cat )
NameError: undefined local variable or method `cat' for main:Object
	from (irb):7
	from /usr/bin/irb:12:in `<main>'
irb(main):008:0> Integer( "cat" )
ArgumentError: invalid value for Integer(): "cat"
	from (irb):8:in `Integer'
	from (irb):8
	from /usr/bin/irb:12:in `<main>'
def convert_number( number )
  begin
    Integer( number )
  rescue ArgumentError
    puts "ArgumentError--not a number"
    nil
  end
end
raise ParserError.new( "Some error statement." )

Ruby Java
begin
  ...
rescue exception
  ...
end
		
try
{
    ...
}
catch( exception )
{
    ...
}
		
raise exception
		
throw exception
		

projects/lexicon/lib/lexicon.rb:

class Lexicon
  attr_accessor :lexicon, :dictionary

  def initialize()
    @dictionary =
    {
      :north    => :direction,
      :south    => :direction,
      :east     => :direction,
      :west     => :direction,
      :go       => :verb,
      :kill     => :verb,
      :eat      => :verb,
      :bear     => :noun,
      :princess => :noun,
      :the      => :stop,
      :in       => :stop,
      :of       => :stop,
    }
  end

  Pair = Struct.new( :qualifier, :value )

  def scan(list)
    pairs = []
    list.split.map do | value |
      puts value
      if value.match( /^[0-9]+$/ )
        pairs.push( Pair.new( :number, value.to_i ) )
      elsif qualifier = @dictionary[ value.intern ]
        pairs.push( Pair.new( qualifier, value ) )
      else
        pairs.push( Pair.new( :error, value.to_s ) )
      end
    end
    pairs
  end
end

projects/lexicon/test/test_lexicon.rb:

require 'test/unit'
require_relative "../lib/lexicon/lexicon"

class LexiconTests < Test::Unit::TestCase

  Pair = Lexicon::Pair
  @@lexicon = Lexicon.new()

  def test_directions()
    assert_equal( [ Pair.new( :direction, 'north' ) ], @@lexicon.scan( "north" ) )
    result = @@lexicon.scan( "north south east" )
    assert_equal( result, [ Pair.new( :direction, 'north' ),
                  Pair.new( :direction, 'south' ),
                  Pair.new( :direction, 'east' ) ] )
  end

  def test_verbs()
    assert_equal( @@lexicon.scan("go"), [Pair.new(:verb, 'go' ) ] )
    result = @@lexicon.scan( "go kill eat" )
    assert_equal( result, [ Pair.new( :verb, 'go' ),
                  Pair.new( :verb, 'kill' ),
                  Pair.new( :verb, 'eat' ) ] )
  end

  def test_stops()
    assert_equal( @@lexicon.scan( "the" ), [Pair.new(:stop, 'the' ) ] )
    result = @@lexicon.scan( "the in of" )
    assert_equal( result, [ Pair.new( :stop, 'the' ),
                  Pair.new( :stop, 'in' ),
                  Pair.new( :stop, 'of' ) ] )
  end

  def test_nouns()
    assert_equal( @@lexicon.scan( "bear" ), [Pair.new(:noun, 'bear' ) ] )
    result = @@lexicon.scan( "bear princess" )
    assert_equal( result, [ Pair.new( :noun, 'bear' ),
                  Pair.new( :noun, 'princess' ) ] )
  end

  def test_numbers()
    assert_equal( @@lexicon.scan( "1234" ), [ Pair.new( :number, 1234 ) ] )
    result = @@lexicon.scan( "3 91234 " )
    assert_equal( result, [ Pair.new( :number, 3 ),
                  Pair.new( :number, 91234 ) ] )
  end

  def test_errors()
    assert_equal( @@lexicon.scan( "ASDFADFASDF" ), [ Pair.new( :error, 'ASDFADFASDF' ) ] )
    result = @@lexicon.scan( "bear IAS princess" )
    assert_equal( result, [ Pair.new( :noun, 'bear' ),
                  Pair.new( :error, 'IAS' ),
                  Pair.new( :noun, 'princess' ) ] )
  end
end

$ ruby test_lexicon.rb
Run options:

# Running tests:

north
north
south
east
.ASDFADFASDF
bear
IAS
princess
.bear
bear
princess
.1234
3
91234
.the
the
in
of
.go
go
kill
eat
.

Finished tests in 0.000920s, 6519.4432 tests/s, 13038.8863 assertions/s.

6 tests, 12 assertions, 0 failures, 0 errors, 0 skips

Modules

A module is a way to package up methods so that they don't conflict with anything else in Ruby or your Ruby code. In essence, the Ruby module wraps all the methods with a name. You consume module methods merely by stating the module name, adding the dot operator and then the name of the method you want to use.

Parser.parse_verb( word_list )

Printing

formatter = "%s %s %s %s"

puts formatter % [ 1, 2, 3, 4 ]
puts formatter % [ "one", "two", "three", "four" ]
puts formatter % [ true, false, false, true ]
puts formatter % [ formatter, formatter, formatter, formatter ]
puts formatter % [
    "I had this thing.",
    "That you could type up right.",
    "But it didn't sing.",
    "So I said goodnight."
]
1 2 3 4
one two three four
true false false true
%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s
I had this thing. That you could type up right. But it didn't sing. So I said goodnight.

Now observe the artificial insertion of newlines into a string. Observe also the special PARAGRAPH-delimiting operator and the redirection operator (<<) used to "redirect" the paragraph to the puts method.

days   = "Mon Tue Wed Thu Fri Sat Sun"
months = "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug"

puts "Here are the days: ", days
puts "Here are the months: ", months

puts <<PARAGRAPH
There's something going on here.
With the PARAGRAPH thing
We'll be able to type as much as we like.
Even 4 lines if we want, or 5, or 6.
PARAGRAPH
Here are the days:
Mon Tue Wed Thu Fri Sat Sun
Here are the months:
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
There's something going on here.
With the PARAGRAPH thing
We'll be able to type as much as we like.
Even 4 lines if we want, or 5, or 6.

Input

Ruby's gets method, its name perhaps inspired (just as puts) by the C Standard Library functions, fetches a string from stdin. The exercises will more difficult to document from here on since they'll require interaction. This is, after all, the beginning of script-writing.

The chomp() method truncates while strip() trims.

print "How old are you? "
age = gets.chomp()
print "How tall are you? "
height = gets.chomp()
print "How much do you weigh? "
weight = gets.chomp()

puts "So, you're #{age} years old, #{height} tall and #{weight} heavy."
How old are you? 65
How tall are you? 72
How much do you weigh? 568
So, you're 65 years old, 72 tall and 568 heavy.

Prompting

user = ARGV.first
prompt = '> '

puts "Hi #{user}, I'm the #{$0} script."
puts "I'd like to ask you a few questions."
puts "Do you like me #{user}?"
print prompt
likes = STDIN.gets.chomp()

puts "Where do you live #{user}?"
print prompt
lives = STDIN.gets.chomp()

puts "What kind of computer do you have?"
print prompt
computer = STDIN.gets.chomp()

puts <<MESSAGE
Alright, so you said #{likes} about liking me.
You live in #{lives}.  Not sure where that is.
And you have a #{computer} computer.  Nice.
MESSAGE
$ ruby prompt.rb Jack in the Bean Stalk
Hi Jack, I'm the prompt.rb script.
I'd like to ask you a few questions.
Do you like me ?
> Yes
Where do you live ?
> Here
What kind of computer do you have?
> Hewlett-Packard
Alright, so you said Yes about liking me.
You live in Here.  Not sure where that is.
And you have a Hewlett-Packard computer.  Nice.

File I/O

filename = ARGV.first

prompt = "> "
txt = File.open( filename )

puts "Here's your file: #{filename}"
puts txt.read()
close()

puts "I'll also ask you to type it again:"
print prompt
file_again = STDIN.gets.chomp()

txt_again = File.open( file_again )

puts txt_again.read()
again.close()

Ruby file I/O
open( filename, action ) Opens filename for action {r|w|a}.
close() Closes the file.
read() Reads the file's contents.
readline() Reads a single line from file.
truncate() Empties the open file.
write() Writes to the file if opened with w.
exists Returns true/false that the file exists.

filename = ARGV.first
script = $0

puts "We're going to erase #{filename}."
puts "If you don't want that, hit CTRL-C (^C)."
puts "If you do want that, hit RETURN."

print "? "
STDIN.gets

puts "Opening the file..."
target = File.open(filename, 'w')

puts "Truncating the file.  Goodbye!"
target.truncate(target.size)

puts "Now I'm going to ask you for three lines."

print "line 1: "; line1 = STDIN.gets.chomp()
print "line 2: "; line2 = STDIN.gets.chomp()
print "line 3: "; line3 = STDIN.gets.chomp()

puts "I'm going to write these to the file."

target.write(line1)
target.write("\n")
target.write(line2)
target.write("\n")
target.write(line3)
target.write("\n")

puts "And finally, we close it."
target.close()
from_file, to_file = ARGV
script = $0

puts "Copying from #{from_file} to #{to_file}"

# we could do these two on one line too, how?
input = File.open(from_file)
indata = input.read()

puts "The input file is #{indata.length} bytes long"

puts "Does the output file exist? #{File.exists? to_file}"
puts "Ready, hit RETURN to continue, CTRL-C to abort."
STDIN.gets

output = File.open(to_file, 'w')
output.write(indata)

puts "Alright, all done."

output.close()
input.close()

Libraries

try-library.rb:

require 'open-uri'

open("http://www.ruby-lang.org/en") do |f|
  f.each_line {|line| p line}
  puts f.base_uri         # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
  puts f.content_type     # "text/html"
  puts f.charset          # "iso-8859-1"
  puts f.content_encoding # []
  puts f.last_modified    # Thu Dec 05 02:45:02 UTC 2002
end

If you're behind a firewall, this won't work.

$ export http_proxy="http://web-proxy.austin.hp.com:8080"
$ ruby try-library.rb
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n"
"   \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
"  <head>\n"
"    <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n"
.
.
.

Ruby project layout

You can create a skeleton like this one, then clone it to start a new Ruby project.

$ tree projects
projects
+-- skeleton
    + bin
    + lib
    | + NAME
    | | ` version.rb
    | ` NAME.rb
    + NAME.gemspec
    ` test
        ` test_NAME.rb

5 directories, 4 files

(Note that NAME everywhere is to be replaced by the name of your project.)

NAME.gemspec:
# -*- encoding: utf-8 -*-
$:.push File.expand_path( "../lib", __FILE__ )
require "NAME/version"

Gem::Specification.new do | s |
  s.name        = "NAME"
  s.version     = NAME::VERSION
  s.authors     = [ "Russ Bateman" ]
  s.email       = [ "[email protected]" ]
  s.homepage    = ""
  s.summary     = %q{TODO: Write a gem summary}
  s.description = %q{TODO: Write a gem description}

  s.rubyforge_project = "NAME"

  s.files         = `git ls-files`.split( "\n" )
  s.test_files    = `git ls-files -- { test, spec,features }/*`.split( "\n" )
  s.executables   = `git ls-files -- bin/*`.split( "\n" ).map{ | f | File.basename( f ) }
  s.require_paths = [ "lib" ]
end

Steps to use this skeleton.

  1. When ready to create and code a new project, copy this skeleton to where you wish to work and rename it appropriately.
  2. Rename skeleton, NAME, NAME.gemspec, NAME.rb and test_NAME.rb in the filesystem.
  3. Edit what used to be named NAME.gemspec and change occurrences of NAME as appropriate.

Testing

This is just like JUnit testing...

projects/fun/lib/fun.rb:

class Room
  attr_accessor :name, :description, :paths

  def initialize( name, description )
    @name = name
    @description = description
    @paths = {}
  end

  def go( direction )
    @paths[ direction ]
  end

  def add_paths( paths )
    @paths.update( paths )
  end
end

projects/fun/test/test_fun.rb:

require 'test/unit'
require_relative '../lib/fun'

class MyUnitTests < Test::Unit::TestCase

  def test_room()
    gold = Room.new( "GoldRoom",
                     """This room has gold in it you can grab. There's a
                        door to the north.""" )
    assert_equal( gold.name, "GoldRoom")
    assert_equal( gold.paths, {} )
  end

  def test_room_paths()
    center = Room.new( "Center", "Test room in the center." )
    north = Room.new( "North", "Test room in the north." )
    south = Room.new( "South", "Test room in the south." )

    center.add_paths( { :north => north, :south => south } )
    assert_equal( center.go( :north ), north )
    assert_equal( center.go( :south ), south )
  end

  def test_map()
    start = Room.new( "Start", "You can go west and down a hole." )
    west  = Room.new( "Trees", "There are trees here, you can go east." )
    down  = Room.new( "Dungeon", "It's dark down here, you can go up." )

    start.add_paths( { :west => west, :down => down } )
    west.add_paths( { :east => start } )
    down.add_paths( { :up => start } )

    assert_equal( start.go( :west ), west )
    assert_equal( start.go( :west ).go( :east ), start )
    assert_equal( start.go( :down ).go( :up ), start )
  end
end

$ ruby test_fun.rb
Run options:

# Running tests:

...

Finished tests in 0.000536s, 5599.6790 tests/s, 13065.9176 assertions/s.

3 tests, 7 assertions, 0 failures, 0 errors, 0 skips

Best practices for Ruby testing...

  1. Put test files into the test subdirectory, one file per module or class tested.
  2. Keep test cases short. Test cases tend to be a bit messy.
  3. Keep test cases as clean as the natural messiness will permit. Create helper methods to reduce code duplication.
  4. I say, "Write tests first; then only write the production code based on the output from the tests". In other words, TEST FIRST, CODE LAST!




Appendix


Useful links


Useful links to articles about the gemspec file.


Running Ruby from Ruby

There are myriad ways, some better than others. From Linux, this works well because Ruby implements the backtick.

run-ruby-file.rb:
#!/usr/bin/ruby

puts "Run a selected Ruby file..."
output = `ruby ./hello-world.rb`
puts output
hello-world.rb:
#!/usr/bin/ruby

# Some Ruby code to run...
print "Hello, World!"

$ ruby test-test.rb
Hello, World!

Installing Ruby developer software

Watch and learn. This is the answer to some questions. Ruby has its own package manager named Gem. (This is like Perl and PHP which have their own package managers.) You'll want to get root on your system and approximate the following in order to equip your host with Ruby developer software.

~/dev/ruby # dpkg --list | grep ruby
ii  libruby1.8        1.8.7.352-2ubuntu1.3      Libraries necessary to run Ruby 1.8
ii  libruby1.9.1      1.9.3.0-1ubuntu2.7        Libraries necessary to run Ruby 1.9.1
ii  ruby1.9.1         1.9.3.0-1ubuntu2.7        Interpreter of object-oriented scripting language Ruby
~/dev/ruby # aptitude install ruby1.9.1-dev
The following NEW packages will be installed:
  ruby1.9.1-dev
0 packages upgraded, 1 newly installed, 0 to remove and 14 not upgraded.
Need to get 1,212 kB of archives. After unpacking 4,072 kB will be used.
Get: 1 http://archive.ubuntu.com/ubuntu/ precise-updates/main ruby1.9.1-dev amd64 1.9.3.0-1ubuntu2.7 [1,212 kB]
Fetched 1,212 kB in 4s (276 kB/s)
Selecting previously unselected package ruby1.9.1-dev.
(Reading database ... 183671 files and directories currently installed.)
Unpacking ruby1.9.1-dev (from .../ruby1.9.1-dev_1.9.3.0-1ubuntu2.7_amd64.deb) ...
Setting up ruby1.9.1-dev (1.9.3.0-1ubuntu2.7) ...

~/dev/ruby # gem install rdoc-data
Building native extensions.  This could take a while...
Fetching: rdoc-4.0.1.gem (100%)
Depending on your version of ruby, you may need to install ruby rdoc/ri data:

<= 1.8.6 : unsupported
 = 1.8.7 : gem install rdoc-data; rdoc-data --install
 = 1.9.1 : gem install rdoc-data; rdoc-data --install
>= 1.9.2 : nothing to do! Yay!
Fetching: rdoc-data-4.0.1.gem (100%)
rdoc-data is only required for C ruby 1.8.7 or 1.9.1.

rdoc-data is required for JRuby.

To install ri data for RDoc 4.0+ run:

  rdoc-data --install

Successfully installed json-1.8.0
Successfully installed rdoc-4.0.1
Successfully installed rdoc-data-4.0.1
3 gems installed
Installing ri documentation for json-1.8.0...
Installing ri documentation for rdoc-4.0.1...
Installing ri documentation for rdoc-data-4.0.1...
Installing RDoc documentation for json-1.8.0...
Installing RDoc documentation for rdoc-4.0.1...
Installing RDoc documentation for rdoc-data-4.0.1...
~/dev/ruby # rdoc-data --install

Now that it's installed, it should work, right? Well, no, it doesn't...


Still cannot use ri to get documentation

You get stuff like the following:

~/dev/ruby $ ri File.open
Nothing known about File

...or, after solving some missing Gem package management...

~/dev/ruby $ ri File.open
/var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/store.rb:617:in `initialize': store at \
      /usr/share/ri/1.9.1/system  missing file /usr/share/ri/1.9.1/system/Kernel/open-c.ri \
      for Kernel::open (RDoc::Store::MissingFileError)
	from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/store.rb:617:in `open'
	from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/store.rb:617:in `load_method'
	from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/ri/driver.rb:1185:in `load_method'
  .
  .
  .
	from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/ri/driver.rb:356:in `run'
	from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/bin/ri:12:in `'
	from /usr/local/bin/ri:19:in `load'
	from /usr/local/bin/ri:19:in `
'

This is because the Ruby community doesn't have it all together. I found this suggestion on stackoverflow.com for a different syntax (using the #). And yet, ri File.read does work. Go figure.

~/dev/ruby $ ri File#open
= File#open

(from ruby core)
=== Implementation from Kernel
------------------------------------------------------------------------------
  open(path [, mode_enc [, perm]] [, opt])                -> io or nil
  open(path [, mode_enc [, perm]] [, opt]) {|io| block }  -> obj

------------------------------------------------------------------------------

Creates an IO object connected to the given stream, file, or subprocess.

If path does not start with a pipe character (``|''), treat it as the
name of a file to open using the specified mode (defaulting to ``r'').

The mode_enc is either a string or an integer.  If it is an integer, it must
.
.
.
Open a subprocess using a block to receive the I/O object:

  open("|-") do |f|
    if f == nil
      puts "in Child"
    else
      puts "Got: #{f.gets}"
    end
  end

produces:

  Got: in Child

(END)

...which is wonderful and just what Exercise 15 above was looking for.


More on Ruby information (ri)...

I tried these to find out about the pop() method:

$ ri String       —yes, but doesn't list method pop()
$ ri String.pop   —no
$ ri String.pop() —no
$ ri String#pop   —no
$ ri String#pop() —no
$ ri pop          —yes, but as applies to all objects

Runby debugging best practices

  1. A good way to debug a program is to use puts or p to print out the values of variables at points in the program to see where they go wrong.
  2. Make sure parts of your programs work as you work on them. Do not write massive files of code before you try to run them. Code a little, run a little, fix a little.
  3. Test first, code second.

Is there a Ruby debugger?

Yes, there is! And it's totally ripped off from dbx/gdb, but hey, it works! See How do I debug Ruby scripts?.

    $ ruby -rdebug ruby-file

Debugger help

Debugger help v.-0.002b
Commands
  b[reak] [file:|class:]<line|method>
  b[reak] [class.]<line|method>
                             set breakpoint to some position
  wat[ch] <expression>       set watchpoint to some expression
  cat[ch] (<exception>|off)  set catchpoint to an exception
  b[reak]                    list breakpoints
  cat[ch]                    show catchpoint
  del[ete][ nnn]             delete some or all breakpoints
  disp[lay] <expression>     add expression into display expression list
  undisp[lay][ nnn]          delete one particular or all display expressions
  c[ont]                     run until program ends or hit breakpoint
  s[tep][ nnn]               step (into methods) one line or till line nnn (and press Enter to repeat)
  n[ext][ nnn]               go over one line or till line nnn (and press Enter to repeat)
  w[here]                    display frames
  f[rame]                    alias for where
  l[ist][ (-|nn-mm)]         list program, - lists backwards
                             nn-mm lists given lines
  up[ nn]                    move to higher frame
  down[ nn]                  move to lower frame
  fin[ish]                   return to outer frame
  tr[ace] (on|off)           set trace mode of current thread
  tr[ace] (on|off) all       set trace mode of all threads
  q[uit]                     exit from debugger
  v[ar] g[lobal]             show global variables
  v[ar] l[ocal]              show local variables
  v[ar] i[nstance] <object>  show instance variables of object
  v[ar] c[onst] <object>     show constants of object
  m[ethod] i[nstance] <obj>  show methods of object
  m[ethod] <class|module>    show instance methods of class or module
  th[read] l[ist]            list all threads
  th[read] c[ur[rent]]       show current thread
  th[read] [sw[itch]] <nnn>  switch thread context to nnn
  th[read] stop <nnn>        stop thread nnn
  th[read] resume <nnn>      resume thread nnn
  p expression               evaluate expression and print its value
  h[elp]                     print this help
  <everything else>          evaluate

Gemfile

Gemfile is a file at the root of whatever Ruby project you're creating, including a Chef cookbook, that acts sort of like Maven's .pom in that it lists Ruby software and versions for the bundler to get on which the project depends. For example:

 



Running bundle with this Gemfile produces something like the following. It can take a long time to run.

source "https://rubygems.org"

gem 'net-ssh', "~> 2.2.2"
gem 'net-ssh-gateway'
gem 'chef', "~> 11.0.0"
gem 'chefspec', '>=1.0.0'
gem 'librarian', ">=0.0.26"
gem 'vagrant', "~> 1.0.7"
gem 'knife-block'
gem 'thor'
gem 'rbvmomi'
gem 'icontrol'
		
 
~/ruby-fun $ bundle
Fetching gem metadata from https://rubygems.org/......
Fetching gem metadata from https://rubygems.org/..
Installing archive-tar-minitar (0.5.2)
Installing bindata (1.5.0)
Installing builder (3.2.2)
Installing erubis (2.7.0)
Installing highline (1.6.19)
Installing json (1.6.1)
Using mixlib-log (1.6.0)
Using mixlib-authentication (1.3.0)
Using mixlib-cli (1.3.0)
Using mixlib-config (1.1.2)
Installing mixlib-shellout (1.2.0)
Installing net-ssh (2.2.2)
Installing net-ssh-gateway (1.1.0)
Installing net-ssh-multi (1.1)
Installing ipaddress (0.8.0)
Using systemu (2.5.2)
Installing yajl-ruby (1.1.0)
Installing ohai (6.18.0)
Installing mime-types (1.23)
Installing rest-client (1.6.7)
Installing chef (11.0.0)
Installing multi_json (1.7.7)
Installing multi_xml (0.5.4)
Installing httparty (0.11.0)
Installing fauxhai (1.1.1)
Installing ci_reporter (1.9.0)
Installing minitest (4.7.5)
Installing minitest-chef-handler (1.0.1)
Installing rspec-core (2.14.4)
Installing diff-lcs (1.2.4)
Installing rspec-expectations (2.14.0)
Installing rspec-mocks (2.14.1)
Installing rspec (2.14.1)
Installing chefspec (1.3.1)
Installing ffi (1.9.0)
Installing childprocess (0.3.9)
Installing crack (0.1.8)
Installing gyoku (1.0.0)
Installing rack (1.5.2)
Installing httpi (0.7.9)
Installing i18n (0.6.4)
Installing savon (0.8.6)
Installing icontrol (0.3.9)
Installing knife-block (0.0.6)
Installing thor (0.18.1)
Installing librarian (0.1.0)
Installing log4r (1.1.10)
Installing mini_portile (0.5.1)
Installing net-scp (1.0.4)
Installing nokogiri (1.6.0)
Installing trollop (2.0)
Installing rbvmomi (1.6.0)
Installing vagrant (1.0.7)
Using bundler (1.3.5)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
Post-install message from httparty:
When you HTTParty, you must party hard!
		







Best practice
Odd notation in Ruby
Numbers and math
Symbols
Arguments
Methods (functions)
Logical expressions
Ontological expressions
Conditionals
Control statements (loops)
Arrays
Hash[maps]
Classes and objects
Exception handling and raising
Modules
Printing
Input
Prompting
File I/O
Libraries
Ruby project layout
Testing
Appendix
        Useful links
        Running Ruby from Ruby
        Installing Ruby developer software
        Using ri to get documentation
        More on Ruby ri
        Ruby debugging best practices
        Is there a Ruby debugger?
        Ruby debugger help
        Gemfile