Ruby Course Notes

Author: Dave Kuhlman
Contact: dkuhlman (at) davekuhlman (dot) org
Address:
http://www.davekuhlman.org
Revision: 1.1.1
Date: December 16, 2019
Copyright:Copyright (c) 2013 Dave Kuhlman. All Rights Reserved. This software is subject to the provisions of the MIT License http://www.opensource.org/licenses/mit-license.php.
Abstract:This document provides notes and an outline of an introductory course on programming in Ruby.

Contents

1   Introductions Etc.

1.2   Preliminaries

Text editor -- Use any good programmer text editor. See:

1.2.1   The Ruby interactive shell -- irb

Some notes:

  • irb can be configured from the command line or in your personal configuration file .irbrc in your home directory.

  • For help with configuring irb, see chapter "Interactive Ruby Shell" in Programming Ruby The Pragmatic Programmer's Guide -- http://www.ruby-doc.org/docs/ProgrammingRuby/. Or, (on Linux), use man:

    $ man irb
    
  • For a more simple prompt, try:

    $ irb --prompt=simple
    

    Or, put the following in your ~/.irbrc file:

    IRB.conf[:PROMPT_MODE] = :SIMPLE
    
  • If tab completion is not working for you, try adding the following to your ~/.irbrc file:

    require 'irb/completion'
    

My .irbrc also saves history across sessions. Here is my complete .irbrc:

IRB.conf[:PROMPT][:SIMPLE_DAVE] = { # name of prompt mode
  :PROMPT_I => ">> ",         # normal prompt
  :PROMPT_C => ">> ",         # prompt for continuated statement
  :PROMPT_S => nil,         # prompt for continuated strings
  :RETURN => "    ==>%s\n"       # format to return value
}
#IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history"
require 'irb/completion'
require 'irb/ext/save-history'
require 'pp'
# Make ruby ``print`` act more like Python ``print``.
# Set the field separator and the record separator special variables.
$, = " "
$\ = "\n"
def dir obj
  puts "Class: #{obj.class}  object_id: #{obj.object_id}"
  puts "Methods:", obj.methods.sort.inspect
end

Displaying values, variables, etc -- Use puts, print, and p:

  • puts adds a newline.

  • p obj writes obj.inspect and a newline. When you are debugging code, it's likely that it's p that you want.

  • print does not add a newline.

  • You can modify the behavior of print a, b, c by setting the values of $, and $\\ (the field separator and the record separator variables). Example:

    >> $, = "::"
        ==>"::"
    >> $\ = "\n-----\n"
        ==>"\n-----\n"
    >>
    >>
    >> print 11, 22, 33
    11::22::33
    -----
        ==>nil
    >>
    

    Or, it might be more useful to set the field and record separators as follows:

    >> $, = " "
        ==>" "
    >> $\ = "\n"
        ==>"\n"
    >> print 11, 22, 33
    11 22 33
        ==>nil
    >>
    

    You can do this in your .irbrc file.

  • If you start irb with this command $ irb -rpp, then you can use pp obj to pretty print objects.

  • I do this print some_obj.methods.sort a lot. If you are like me in that way, you can consider adding something like the following to your .irbrc file, so that it will always be available at the irb interactive prompt:

    def dir obj
      puts "Class: #{obj.class}"
      puts "Methods:", obj.methods.sort.inspect
    end
    

    Then use dir(some_object) (with or without parentheses) to display the methods supported by an object.

1.2.2   Running a Ruby script

Run and test a script with one of the following. The second form enables you to use the Ruby debugger:

$ ruby my_script.rb ...
$ ruby -rdebug my_script.rb ...

Also see Running Ruby scripts.

1.3   Additional help and documentation

Here are some places and ways to get help:

1.4   Running Ruby scripts

Run a Ruby script as follows:

$ ruby my_script1.rb
$ ruby my_script2.rb arg1 arg2 arg3

On Linux and Unix-like systems, to make the script itself executable, do the following:

  1. Add the following as the first line of the script:

    #!/usr/bin/env ruby
    
  2. And, change the permission of the file to executable. To do so, either use your file manager, or from the command line do something like this:

    $ chmod u+x my_script.rb
    

In order to make your script both "run-able" and usable as a library (with require), use the following at the bottom of the script to start processing when it is run, but not when it is imported (with require):

if __FILE__ == $0
  main()
end

Notes:

  • The above example assumes that when you run this script, you want to call the function main.

  • The command line arguments are available within your script in the pre-defined variables ARGV or $*.

  • If you need to parse and process command line options (prefixed with "-" or "--"), consider using the optparse module.

  • In order to use the Ruby debugger, run your script with the -rdebug1 flag, for example:

    $ ruby -rdebug my_scipt.rb
    

    Also see Debugging ruby scripts

1.5   Inspecting and examining Ruby objects

The following may be helpful:

>> p obj
>> puts obj.methods.sort.inspect

And, you could consider adding the following definition to your .irbrc file:

# A quick way to display object info and a sorted list of its methods.
def dir obj
  puts "Class: #{obj.class}  object_id: #{obj.object_id}"
  puts "Methods:", obj.methods.sort.inspect
end

1.6   Viewing Ruby documentation with ri

The ri command line documentation viewer is a very useful way to learn about Ruby modules, classes, and their methods.

If it is not already installed, do this:

$ sudo gem install rdoc-data
$ rdoc-data --install

Then you should be able to display documentation by doing things like the following:

$ ri Array
$ ri Array.each_with_index
$ ri String.slice

And, if the item for which you want documentation is in a module, try things like this:

$ ri Asciidoctor::Document
$ ri Asciidoctor::Document.blocks

Note: The above works on my machine because I have AsciiDoctor installed. AsciiDoctor is written in Ruby, by the way.

And, if you want an interactive prompt with tab name completion, then run:

$ ri -i

For more help with ri and rdoc, see: http://jstorimer.com/ri.html

2   Lexical matters

2.1   Lines

Ruby is line oriented. Typically you will write one Ruby statement per line.

You could write more than one statement on a single line separated by semicolons, but it is considered poor form.

A single statement on a line may end in a semicolon, but the semicolon is unnecessary, and bad style.

A statement may continue across more than one line as long as the proceeding line (1) ends in an operator or (2) is an open context (inside "()", "[]", or "{}"), or (3) ends with a backslash as the last character.

2.2   Names and tokens

Names in ruby are composed of letters, digits, underscores, and a few initial special characters. In some cases, those special characters determine the scope of the variable, as described in this table:

Initial character Variable Scope
$ A global variable
[a-z] or _ A local variable
@ An instance variable
@@ A class variable (also global within a module)
[A-Z] A constant

This table can be found at: http://www.techotopia.com/index.php/Ruby_Variable_Scope

2.2.1   Predefined Ruby global variables

Variable Name Variable Value
$@ The location of latest error
$_ The string last read by gets
$. The line number last read by interpreter
$& The string last matched by regexp
$~ The last regexp match, as an array of subexpressions
$n The nth subexpression in the last match (same as $~[n])
$= The case-insensitivity flag
$/ The input record separator
$ The output record separator
$0 The name of the ruby script file currently executing
$* The command line arguments used to invoke the script
$$ The Ruby interpreter's process ID
$? The exit status of last executed child process
$stdin The standard input file
$stdout The standard output file
$stderr The standard error output file

This table can be found at: http://www.techotopia.com/index.php/Ruby_Variable_Scope

A few more variables that are automatically available in your scripts:

  • The magic variable __FILE__, which contains the name of the current file.
  • The variable ARGV, which is a synonym for $*.

Also see a more extensive list of pre-defined variables at: http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Variables_and_Constants#Pre-defined_Variables

2.3   Blocks and indentation

The Ruby convention for indentation is 2 spaces per indentation level. Do not use hard tabs.

Ruby code blocks are delimited by keywords such as if <block> end, do <block> end, etc. Curley brackets ({ and }) can also be used to surround a block in some situations.

2.4   Comments

Everything to the right of a "#" character on any line is a comment.

Block comments -- You can use Ruby's =begin and =end comment markers. For example:

=begin
This comment spans
several lines.
=end

2.5   Program structure

  • Files
  • Modules
  • Classes
  • Functions
  • Structured statements (if, while, etc) and blocks. A block is surrounded by do-end or by curly brackets. Structured statements contain nested blocks; they begin with a keyword (for example, if, while, until, case, etc.) and end with the keyword end. Ruby does have a for statement, but you will usually use and iterator instead (one of the each_xxx methods.

2.6   Operators

You can find a summary of Ruby's operators here: Ruby operators.

And, the precedence of Ruby operators can be found here: Ruby operator precedence.

Note that the meaning of an individual operator may be different depending on the objects it is applied to.

2.7   Code evaluation

When Ruby evaluates the code in a script file, it evaluates that code from the top of the file down to the bottom. Therefore, the order of code in that file may affect the behavior of the script. In particular, functions must be defined before they are called.

2.8   Simple script structure

Here is a simple script that can be run from the command line:

def main
  puts "hello"
end

main

Notes:

  • The above script defines one method (main), then calls that function.

Here is another, slightly less simple script that can be run from the command line or can be used in another script:

#!/usr/bin/env ruby

# simple02.rb

def print_upcase_message msg
  puts msg.upcase
end

def main
  print_upcase_message "aBcDeFgH"
end

if __FILE__ == $0
  main
end

Notes:

  • The above script defines two methods: print_upcase_message and main.
  • When run from the command line, the function main is called.
  • When used in another script via the require, main is not called (until the script that uses it does so explicitly).

Here is an example of the use of this script:

>> require './simple02.rb'
    ==>true
>> print_upcase_message 'aaaBBBccc'
AAABBBCCC
    ==>nil

3   Built-in data-types

There are no builtin datatypes in Ruby. Every data object is an instance of some class. So, for information on the equivalant of some of Python's builtin datatypes, see the documentation on the appropriate class, e.g. String, Integer, Array, File, etc. at: Ruby-Doc.org -- http://ruby-doc.org/. Many of these datatypes are in the Ruby core, which means that you do not need to use the require command in order to use them.

Also, in order to get more information on a class or one of its methods, do at the command line:

$ ri String
$ ri "String.scan"

etc.

And, inside the ruby interactive prompt, irb, you can get a list of methods available for an object by typing:

>> some_obj.methods.sort

3.1   Numeric types

See these classes:

Ruby numeric operators will automatically coerce integers to floats when you use mixed arithmetic. Example:

>> 3 / 6
    ==>0
>> 3 / 6.0
    ==>0.5

You can also explicitly convert an integer (or Fixnum) to a float. Example:

>> a = 5
    ==>5
>> b = 3
    ==>3
>> a / b
    ==>1
>> a.to_f / b
    ==>1.6666666666666667

3.2   Symbols (atoms)

Symbols are names in Ruby. They are called atoms in some other languages. Create a symbol using the colon for a literal representation. Symbols are singleton objects, which means that you can test to determine whether two symbols are the same symbol using an identity test (.equal?). You can also create them from strings. Examples:

>> a = :aaa
    ==>:aaa
>> b = :aaa
    ==>:aaa
>> a.equal? b
    ==>true
>> a.object_id
    ==>430088
>> b.object_id
    ==>430088

You can also create symbols from strings. Example:

>> c = 'snowpea'
    ==>"snowpea"
>> d = c.to_sym
    ==>:snowpea
>> d
    ==>:snowpea
>> d.class
    ==>Symbol
>> 'oak tree'.to_sym
    ==>:"oak tree"

3.3   Arrays

Information on class Array:

Use square brackets and commas to give a literal representation of an Array and to create it. Use the length method to get the number of items in an Array. Example:

>> a = [11, 22, 33]
    ==>[11, 22, 33]
>> a.class
    ==>Array
>> a.length
    ==>3

3.3.1   Iteration -- Array.each etc

To iterate over the items in an Array, use one of the .each methods:

  • each
  • each_cons
  • each_entry
  • each_index
  • each_slice
  • each_with_index
  • each_with_object

Example:

>> a1 = [111, 222, 333]
    ==>[111, 222, 333]
>> a1.each { |x| puts "item: #{x}" }
item: 111
item: 222
item: 333
    ==>[111, 222, 333]
>> a1.each_index { |idx, x| puts "#{idx}. item: #{x}" }
0. item:
1. item:
2. item:
    ==>[111, 222, 333]
>> a1.each_with_index { |x, idx| puts "#{idx}. item: #{x}" }
0. item: 111
1. item: 222
2. item: 333
    ==>[111, 222, 333]

Index and select an item in an Array with square brackets and an integer. Negative numbers index from the right-hand side of the array. Example:

>> a = (1 .. 9).to_a
    ==>[1, 2, 3, 4, 5, 6, 7, 8, 9]
>> a[0]
    ==>1
>> a[2]
    ==>3
>> a[-1]
    ==>9
>> a[-2]
    ==>8

3.3.2   Slice notation

Ruby has a "slice" notation:

  • a[n, m] produces an Array of m elements starting at a[n].
  • a[n .. m] produces an Array of the elements from a[n] to a[m], including a[m].
  • a[n ... m] produces an Array of the elements from a[n] to a[m], excluding a[m].

Example:

>> a = (1 .. 9).to_a
    ==>[1, 2, 3, 4, 5, 6, 7, 8, 9]
>> a[2, 3]
    ==>[3, 4, 5]
>> a[2 .. 6]
    ==>[3, 4, 5, 6, 7]
>> a[2 ... 6]
    ==>[3, 4, 5, 6]

Test for membership in an Array with the .includes? method. Example:

>> a = [11, 22, 33]
    ==>[11, 22, 33]
>> a.include? 22
    ==>true
>> a.include? 44
    ==>false

In order to use an Array as a stack, use its push and pop methods. Example:

>> pile = []
    ==>[]
>> pile.push 'apple'
    ==>["apple"]
>> pile.push 'banana'
    ==>["apple", "banana"]
>> pile
    ==>["apple", "banana"]
>> pile.pop
    ==>"banana"
>> pile
    ==>["apple"]

The .push method can take multiple arguments.

Instead of .push, you can use the << operator. Because the << operator returns the array itself, it can be chained. Example:

>> container = [:peach, :nectarine]
    ==>[:peach, :nectarine]
>> container << :apricot << :tangerine << :cherry
    ==>[:peach, :nectarine, :apricot, :tangerine, :cherry]
>> container
    ==>[:peach, :nectarine, :apricot, :tangerine, :cherry]

And, you can add the contents of an Array to another Array with the += operator. Example:

>> a = [:aa, :bb]
    ==>[:aa, :bb]
>> a += [:cc, :dd]
    ==>[:aa, :bb, :cc, :dd]
>> a
    ==>[:aa, :bb, :cc, :dd]

3.3.3   map, select, reject, reduce, etc

Ruby also has some higher order functions that work on Arrays and on Enumerators, also. These include the following and their synonyms:

  • map and collect
  • select and find_all
  • reject
  • map and inject

Here are a few of them:

  • an_array.collect { |item| block } -- Return an Array containing each object returned by block. Example:

    >> a = [1, 2, 3, 4, 5, 6]
        ==>[1, 2, 3, 4, 5, 6]
    >> a.collect { |item| item * 3 }
        ==>[3, 6, 9, 12, 15, 18]
    
  • an_array.select { |item| block } -- Return an Array containing each item in an_array for which block returns true. Example:

    >> a = [1, 2, 3, 4, 5, 6]
        ==>[1, 2, 3, 4, 5, 6]
    >> a.select { |item| item % 2 == 0 }
        ==>[2, 4, 6]
    
  • an_array.reject { |item| block } -- Return an Array containing each item in an_array for which block returns false. Example:

    >> a = [1, 2, 3, 4, 5, 6]
        ==>[1, 2, 3, 4, 5, 6]
    >> a.reject { |item| item % 2 == 0 }
        ==>[1, 3, 5]
    
  • an_array.reduce(initial) { |accum, item| block } -- Return the result returned by block after applying it to accum and each item in the array. Example:

    >> a = [1, 2, 3, 4, 5, 6]
        ==>[1, 2, 3, 4, 5, 6]
    >> a.reduce([]) { |accum, item| if item % 2 == 0 then accum.push item * 3 end; accum }
        ==>[6, 12, 18]
    

    Note that "inject" is another name for "reduce". Also note that there are forms of reduce without the arguments and without the block; see the Ruby documentation for more on that.

  • an_array.zip(other_array1, other_array2, ...) -- Return an Array of Arrays. The first item contains the first item from each initial arrays; the second item contains the second items; etc. Example:

    >> a = [1, 2, 3]
        ==>[1, 2, 3]
    >> b = [11, 22, 33]
        ==>[11, 22, 33]
    >> c = [111, 222, 333]
        ==>[111, 222, 333]
    >> a.zip(b, c)
        ==>[[1, 11, 111], [2, 22, 222], [3, 33, 333]]
    

3.4   Strings

Mutable -- Strings are mutable; they can be modified in place. Use my_string.methods.sort in irb, then look for methods that end with a "!".

But, any individual string can be frozen so that it cannot be modified. For example:

>> desc = "A cute cat"
    ==>"A cute cat"
>> desc
    ==>"A cute cat"
>> # Change a slice (using a range) of the string.
>> desc[2..5] = "big"
    ==>"big"
>> desc
    ==>"A big cat"
>> # Make the string immutable.
>> desc.freeze
    ==>"A big cat"
>> # Prove that we cannot modify a frozen string.
>> desc[2..5] = "fast"
RuntimeError: can't modify frozen String
        from (irb):47:in `[]='
        from (irb):47
        from /usr/bin/irb:12:in `<main>'

3.4.1   Multi-byte characters

Ruby strings can contain multibyte characters. Here is an example:

city = "Sel\u00e7uk"
    ==>"Sel\u00E7uk"
puts city
Selçuk
    ==>nil
city.each_char { |x| puts x }
S
e
l
ç
u
k
    ==>"Sel\u00E7uk"

Strings are represented internally as a sequence of characters, not bytes. Those characters can contain unicode characters. Example:

>> puts "a\u011fb"
ağb
    ==>nil
>> puts "a\u011fb".length
3
    ==>nil
>> puts "a\u011fb"[0]
a
    ==>nil
>> puts "a\u011fb"[1]
ğ
    ==>nil
>> puts "a\u011fb"[2]
b
    ==>nil

Multibyte files -- For help with processing files that contain multibyte characters, see section Multibyte encodings in this document.

3.4.2   String literals

Double quotes and single quotes can be used to define strings. Interpolation and backslash character escapes can be used within double quotes. Interpolation and backslash character escapes are treated literally inside single quotes. Example:

>> x = 3
    ==>3
>> 'x: #{x}'
    ==>"x: \#{x}"
>> "x: #{x}"
    ==>"x: 3"
>> sprintf 'x: %s', x
    ==>"x: 3"
>> '\t'
    ==>"\\t"
>> '\t'.length
    ==>2
>> "\t"
    ==>"\t"
>> "\t".length
    ==>1

3.4.2.1   Bachslash escape sequences

Here is a table that describes them:

Escape Sequence Meaning
\n newline (0x0a)
\s space (0x20)
\r carriage return (0x0d)
\t tab (0x09)
\v vertical tab (0x0b)
\f formfeed (0x0c)
\b backspace (0x08)
\a bell/alert (0x07)
\e escape (0x1b)
\nnn character with octal value nnn
\xnn character with hexadecimal value nn
\unnnn Unicode code point U+nnnn (Ruby 1.9 and later)
\cx control-x
\C-x control-x
\M-x meta-x
\M-\C-x meta-control-x
\x character x itself (\" a quote character, for example)

3.4.2.2   Interpolation

Use "#{expression}" within a double quoted string, where expression can be almost any ruby code. Example:

>> size = 35
    ==>35
>> description = "pretty"
    ==>"pretty"
>> "double size: #{size * 2}"
    ==>"double size: 70"
>> "upper description: #{description.upcase}"
    ==>"upper description: PRETTY"

3.4.2.3   The % notation

Use % followed by a non-alpha-numeric character. This notation is especially useful when you want to include double quote and single quote characters in your string. Brackets are especially handy. Brackets can be contained within brackets if they balance. Example:

>> %[this contains "quote" characters, doesn't it]
    ==>"this contains \"quote\" characters, doesn't it"
>> %[this on has [embedded] brackets]
    ==>"this on has [embedded] brackets"

3.4.2.4   Modifiers for the % notation

Here is a table of possible modifiers:

Modifier Meaning
%q[ ] Non-interpolated String (except for \ [ and ])
%Q[ ] Interpolated String (default)
%r[ ] Interpolated Regexp (flags can appear after the closing delimiter)
%i[ ] Non-interpolated Symbol (after Ruby 2.0)
%I[ ] Interpolated Symbol (after Ruby 2.0)
%w[ ] Non-interpolated Array of words, separated by whitespace
%W[ ] Interpolated Array of words, separated by whitespace
%x[ ] Interpolated shell command

3.4.2.5   The "here document" notation

Use "<<DELIMITER", where DELIMITER can be any string. This is handy for multi-line strings. New-line characters are preserved. Example:

>> long_msg = <<END
This is a long message of
fluff fluff fluff fluff
fluff fluff fluff fluff
fluff fluff fluff fluff
fluff fluff fluff fluff
END
    ==>"This is a long message of\nfluff fluff fluff fluff\nfluff fluff fluff fluff\nfluff fluff fluff fluff\nfluff fluff fluff fluff\n"
>> puts long_msg
This is a long message of
fluff fluff fluff fluff
fluff fluff fluff fluff
fluff fluff fluff fluff
fluff fluff fluff fluff
    ==>nil

Interpolation is performed inside a "here document" string, except that if you use single quotes around the delimiter, it's not.

And, you can "stack" multiple here documents:

string = [<<ONE, <<TWO, <<THREE]
  the first thing
ONE
  the second thing
TWO
  and the third thing
THREE

=> ["the first thing\n", "the second thing\n", "and the third thing\n"]

This example came from the Ruby Wiki. See that document for more on string literals: http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals#Strings

3.4.2.6   sprintf string formatting and the % operator

sprintf provides capabilities similar to string interpolation with #{expr}, but with more control over formatting.

The % operator can be used instead of sprintf. For example:

>> a = 5
    ==>5
>> b = 'abcd'
    ==>"abcd"
>> puts "%03d -- %s" % [a, b]
005 -- abcd
    ==>nil

See the docs in the Kernel module: http://ruby-doc.org/core-2.0/Kernel.html#method-i-sprintf In particular, the formatting flags, type codes, width, and precision specification are described there.

Here are several examples:

>> sprintf("%f %d", 1.23, 54)
    ==>"1.230000 54"
>> sprintf "letter A: %x %d", 'A'.ord, 'A'.ord
    ==>"letter A: 41 65"
>> sprintf "%.4f %g", 1.23, 300000
    ==>"1.2300 300000"
>> sprintf "%.4f %g", 1.23, 30000000000
    ==>"1.2300 3e+10"
>> "abcd".ljust 20
    ==>"abcd                "
>> "abcd".rjust 20
    ==>"                abcd"
>> "abcd".center 20
    ==>"        abcd        "
>> sprintf "%20s", "abcd"        # right justify
    ==>"                abcd"
>> sprintf "%-20s", "abcd"       # left justify
    ==>"abcd                "

3.4.3   String concatenation

You can use +, <<, and .concat to concatenate strings. The << and .concat operations modify a string in place, and are therefore faster than + when used to accumulate large strings.

But, use string interpolation ("...#{expr}..."), rather than concatenation, when formatting a string in a single operation.

3.4.4   Converting strings to arrays and arrays to strings

To convert an Array of strings into a single string, use the Array's .join method. Example:

>> a = ['the', 'bright', 'blue', 'balloon']
    ==>["the", "bright", "blue", "balloon"]
>> a.join
    ==>"thebrightblueballoon"
>> a.join ' '
    ==>"the bright blue balloon"
>> a.join '||'
    ==>"the||bright||blue||balloon"

To convert an Array of some other kind of objects into a single string, consider using the Array.collect method. For example:

>> b = [11, 22, 33, 44]
    ==>[11, 22, 33, 44]
>> c = b.collect {|x| x.to_s }
    ==>["11", "22", "33", "44"]
>> c.join "||"
    ==>"11||22||33||44"

Although, the Array.join method seems to do some conversion of items to strings automatically, and even seems to handle Arrays nested inside Arrays.

To convert a String into an Array, use the String's .split method. Example:

>> b = "the bright blue balloon"
    ==>"the bright blue balloon"
>> b.split
    ==>["the", "bright", "blue", "balloon"]
>> b.split(/\W/)
    ==>["the", "bright", "blue", "balloon"]
>> b = "the bright\tblue balloon"
    ==>"the bright\tblue balloon"
>> b.split
    ==>["the", "bright", "blue", "balloon"]
>> b = 'abcd'
    ==>"abcd"
>> b.split(//)
    ==>["a", "b", "c", "d"]
>>
>> # Use a regular expression to split on punctuation as well
>> # as white space.
>> data = "aaa bbb, ccc. \"ddd eee\" fff"
    ==>"aaa bbb, ccc. \"ddd eee\" fff"
>> data.split /\W+/
    ==>["aaa", "bbb", "ccc", "ddd", "eee", "fff"]

Notes:

  • The default delimiter (separator) is white space, when no pattern is given.

  • You can use a regular expression to split on more complicated patterns. For example, "/\W+/" will split off punctuation as well as white space. See example above.

  • Use the empty pattern (//) in order to split a string into individual characters. The pattern // matches between each character. For more on regular expressions, see: http://www.ruby-doc.org/core-2.1.2/Regexp.html. Or, if you have Ruby doc support, do $ ri regexp.

  • But remember that you can process the characters in a String using its .each_char method and a block. So, in many cases, it will not be necessary to convert the String to an Array. Example:

    >> b = 'abcd'
        ==>"abcd"
    >> b.each_char { |ch| puts "ch: #{ch}" }
    ch: a
    ch: b
    ch: c
    ch: d
        ==>"abcd"
    

3.5   Hash -- dictionary

In Ruby, a dictionary is called a Hash. See:

Here is the literal syntax for a hash:

>> table = {"peach" => "tasty", "necartine" => "tangy"}
    ==>{"peach"=>"tasty", "necartine"=>"tangy"}

Or, if the keys are atoms, you can use this alternative literal syntax:

>> table = {peach: "tasty", nectarine: "tangy"}
    ==>{:peach=>"tasty", :nectarine=>"tangy"}

You can mix these two syntaxes in the same literal expression:

>> table = {peach: "tasty", nectarine: "tangy", "tree" => "branchy"}
    ==>{:peach=>"tasty", :nectarine=>"tangy", "tree"=>"branchy"}
>> table
    ==>{:peach=>"tasty", :nectarine=>"tangy", "tree"=>"branchy"}

Then use square brackets to add, replace, and retrieve items:

>> # Add an item.
>> table[:apricot] = "sweet"
    ==>"sweet"
>> table
    ==>{:peach=>"tasty", :nectarine=>"tangy", :apricot=>"sweet"}
>> # Replace an item.
>> table[:peach] = "succulent"
    ==>"succulent"
>> # Retrieve a value from the table.
>> puts table[:nectarine]
tangy
    ==>nil

Remove a key/value pair from a hash -- Example:

>> table
    ==>{:peach=>"tasty", :nectarine=>"tangy", :banana=>"rich"}
>> table.delete(:banana)
    ==>"rich"
>> table
    ==>{:peach=>"tasty", :nectarine=>"tangy"}

Check to determine whether a key is in the hash, use has_key?, include?, or member?. They are all synonyms:

>> table
    ==>{:peach=>"tasty", :nectarine=>"tangy"}
>> table.has_key? :banana
    ==>false
>> table.has_key? :peach
    ==>true

What can I use as a key in a Hash? -- You can use any object that responds to the method .hash. However, ...

Caution: If you add a value to a Hash using a mutable object (for example an Array) as the key in a hash and then you modifiy that object (for example, add an item to the Array), in some cases that mutable object can no longer be used to successfully access that value. Suggestion: if you intend to use an array as a key in a hash, consider using myarray.freeze to make that array immutable.

You can also set a default value to be returned when you as for the value of a key that does not exist in the dictionary. If you do not explicitly set the default, then it is nil. In other words, the default for the default is nil. Example:

>> table = Hash.new('nothing')
    ==>{}
>> p table[:abc]
"nothing"
    ==>"nothing"
>> table.default = 'something'
    ==>"something"
>> p table[:abc]
"something"
    ==>"something"

3.6   Files

Use class File. See:

File.open opens a file for read, write, append, etc access and creates a File object. Once you have opened a file (and created a File object), you can learn about it by typing the following at the irb interactive prompt:

> my_file = File.open('some_file.txt', "r")
> my_file.methods.sort

And, here is a piece of code that reads a text file and writes out each line capitalized:

def read_file(infilename)
  infile = File.open(infilename, "r")
  infile.each do |line|
    puts line.upcase
  end
  infile.close
end

read_file("test.txt")

Notes:

And, the following example writes several lines to a text file:

def write_file outfilename
  data = [
    "line 1",
    "line 2",
    "line 3",
    "line 4",
  ]
  outfile = File.open outfilename, "w"
  data.each_with_index {|item, idx|
    outfile.write "#{idx + 1}. #{item}\n"
  }
  outfile.close
end

write_file "tmp01.txt"

Notes:

  • We need to explicitly add the new line character ("\n") to the end of each line.
  • The method Array.each_with_index gives us an iterator that provides both the next item in the Array and an index (starting at 0) for each iteration.

3.6.1   Standard out and standard err

You can write to stdout and stderr as follows:

$stdout.puts "This is a routine message"
$stderr.puts "This is a error message"

3.6.2   Standard input

You can read the input that was "piped" to your program/script by using file $stdin. Look for the various .read and .each methods.

Here is a sample script that reads standard input ($stdin), converts text to upper case, and writes to standard output ($stdout):

def filter
  lines = $stdin.readlines
  lines.each do |line|
    line = line.upcase
    $stdout.write line
  end
end

filter

If the above script is in a file filter.rb, then you can run it as follows:

$ cat filter.rb | ruby filter.rb
DEF FILTER
  LINES = $STDIN.READLINES
  LINES.EACH DO |LINE|
    LINE = LINE.UPCASE
    $STDOUT.WRITE LINE
  END
END

FILTER

3.6.3   Multibyte encodings

You will sometimes need to process files containing multibyte encodings. To do so, specify the encoding when you open the file. For example:

f = open("testdata01.txt", "r:utf-8")
    ==>#<File:testdata01.txt>
f.each { |line| puts "line: #{line}" }
line: aaa
line: bbb
line: çöğ
line: ccc
    ==>#<File:testdata01.txt>
f.close
    ==>nil

To process individual characters (as opposed to bytes) look for methods .chars, .each_chars, etc in data read from the file or in the file object itself. Also note that my_str[n] indexes and selects characters, not bytes, and that my_str.length returns the number of characters in the string, not the number of bytes.

3.7   Regular expressions

Define regular expressions with a pattern inside forward slashes. Example:

>> pattern1 = /x*(abc)y*(def)z*/

If forward slashes are not a convenient delimiter (for examle, because your pattern contains forward slashes, then you can create a regular expression as follows:

>> pattern2 = Regexp.new("x*(abc)y*(def)z*")

Use a regular expression with any of these operators:

  • =~ -- Returns the position of a match within the target if sucessful, else nil. Use it to determine whether or not there is a successful match (nil indicates failure) and to determine the location of the match in the target. Example:

    >> "xxabbcyyydeeefzzzz" =~ /x*(ab*c)y*(de*f)z*/
        ==>0
    >> $&
        ==>"xxabbcyyydeeefzzzz"
    >> $~
        ==>#<MatchData "xxabbcyyydeeefzzzz" 1:"abbc" 2:"deeef">
    >> $1
        ==>"abbc"
    >> $2
        ==>"deeef"
    >> "aaabbbcccdddeee" =~ /ccc/
        ==>6
    

    Note the use of the special variables $&, $~, $1, $2, etc. to obtain the results of the last match. But, a better way is to use target.match(regexp) or regexp.match(target), which return a MatchData object.

  • regexp_pattern.match(target_string) -- Similar to the use of =~, except that the return value is a MatchData object or nil if the match is not successful. Use it to extract the characters from the target that are matched by groups in the pattern. (Groups are surrounded by parentheses in the Regexp.) Example:

    >> /x*(ab*c)y*(de*f)z*/.match("xxabbcyyydeeefzzzz")
        ==>#<MatchData "xxabbcyyydeeefzzzz" 1:"abbc" 2:"deeef">
    >> /x*(ab*c)y*(de*f)z*/.match("xxabbcyyydeeefzzzz").captures
        ==>["abbc", "deeef"]
    >> /x*(ab*c)y*(de*f)z*/.match("123")
        ==>nil
    >> md = "aaa111bbb222ccc".match(/[^\d]*(\d+)[^\d]*(\d+)/)
        ==>#<MatchData "aaa111bbb222" 1:"111" 2:"222">
    >> md.captures
        ==>["111", "222"]
    
  • !~ -- Returns false if the match is successful, else true. As a memory aid, remember that != reverses the sense of ==. Example:

    >> "111222333" !~ /222/
        ==>false
    >> $&
        ==>"222"
    >> $~
        ==>#<MatchData "222">
    >> "111222333" !~ /555/
        ==>true
    >> $&
        ==>nil
    

MatchData objects -- Here are some things you can do with the MatchData objects that are returned by regexp_pattern.match(target_string) and that is the value of the $~ special variable after a successful match:

>> mdobj = /x*(ab*c)y*(de*f)z*/.match("xxabbcyyydeeefzzzz")
    ==>#<MatchData "xxabbcyyydeeefzzzz" 1:"abbc" 2:"deeef">
>> mdobj
    ==>#<MatchData "xxabbcyyydeeefzzzz" 1:"abbc" 2:"deeef">
>> mdobj.class
    ==>MatchData
>> mdobj.length
    ==>3
>> mdobj[0]
    ==>"xxabbcyyydeeefzzzz"
>> mdobj[1]
    ==>"abbc"
>> mdobj[2]
    ==>"deeef"
>> mdobj.regexp
    ==>/x*(ab*c)y*(de*f)z*/

There are other objects that have methods that take a Regexp as an argument. String.match and String.scan are two useful ones.

Here is a pattern that you can use to search a string for repeating occurances of a pattern and process each one:

$target1 = "111aabb222aaabbb333aaaabbbb444"
$pat1 = /\d(a+)(b+)\d/

def test
  puts "pattern: #{$pat1}"
  puts "target: #{$target1}"
  pos = 0
  while md = $target1.match($pat1, pos)
    puts "#{pos} #{md[0]} #{md[1]} #{md[2]}"
    pos = md.end(0)
  end
  puts "last pos: #{pos}"
end

test

When run, this script displays the following:

$ ruby regexp_example.rb
pattern: (?-mix:\d(a+)(b+)\d)
target: 111aabb222aaabbb333aaaabbbb444
0 1aabb2 aa bb
8 2aaabbb3 aaa bbb
17 3aaaabbbb4 aaaa bbbb
last pos: 28

3.8   Other built-in types

3.8.1   Symbols

Symbols are marked with a leading colon, for example: :apple, :red, :monday.

Symbols stand for themselves. They are singletons. Two symbols that are spelled the same are guarantteed to be identical: they have the same object ID (returned by .object_id).

You can test for the identity of two symbols (whether they are the same symbol) with .equal?. Example:

>> s1 = :tomato
    ==>:tomato
>> s2 = :eggplant
    ==>:eggplant
>> s3 = :tomato
    ==>:tomato
>> s1.equal? s2
    ==>false
>> s1.equal? s3
    ==>true

Notes:

  • Symbols are often a good choice for use as keys in a Hash (dictionary).
  • Symbols are not garbage collected, whereas strings are. So, if your program needs a huge amount of symbols, consider the memory impact.

3.8.2   The nil/None value/type

In Ruby, "None" is called "nil". It is an instance of class NilClass.

You can test for nil with either of the following:

> my_obj.nil?
> not my_obj.nil?

3.8.3   Boolean values

Ruby's boolean values are true and false.

In the condition of Ruby statements like if, while, until, etc, the values false and nil count as false; everything else is true.

3.8.4   Ranges

A range is an instance of class Range.

Ranges are defined with n .. m or n ... m (m >= n):

  • .. includes the last item (m).
  • ... excludes the last item (m).

The end points n and m can be integers, floats, characters, strings, ... The end point objects must be comparable with <=>.

With some ranges, you can use range.to_a to produce an Array from a Range.

Test whether an object is "in" a Range with range.include? obj.

Use methods in the .each family to iterate over elements in a range.

You can also create ranges with Range.new(start, end, exclusive=false).

Example:

>> range1 = 2 .. 4
    ==>2..4
>> range2 = 2 ... 4
    ==>2...4
>> range1.to_a
    ==>[2, 3, 4]
>> range2.to_a
    ==>[2, 3]
>> range1.each { |item| puts "item: #{item}" }
item: 2
item: 3
item: 4
    ==>2..4
>> range3 = 4.5 .. 8.6
    ==>4.5..8.6
>> range3.include? 7.1
    ==>true
>> range3.include? 10.8
    ==>false

3.8.5   Sets and frozensets

Sets are defined in a separate module. Use require to make it available. Here is an example:

>> require 'set'
    ==>true
>>
>> # Create a set of atoms.
>> basket1 = Set.new [:apple, :banana, :nectarine, :peach, :watermelon]
    ==>#<Set: {:apple, :banana, :nectarine, :peach, :watermelon}>
>> # Add an item to the set.
>> basket1.add :cantaloupe
    ==>#<Set: {:apple, :banana, :nectarine, :peach, :watermelon, :cantaloupe}>
>> basket2 = Set.new [:apple, :banana]
    ==>#<Set: {:apple, :banana}>
>> # Test whether a set contains an atom.
>> basket2.include? :nectarine
    ==>false
>> basket1.include? :nectarine
    ==>true
>> basket2.add :apricot
    ==>#<Set: {:apple, :banana, :apricot}>
>> # Get the union of two sets.
>> basket1.union basket2
    ==>#<Set: {:apple, :banana, :nectarine, :peach, :watermelon, :cantaloupe, :apricot}>
>> # Check to see if basket1 was modified.
>> basket1
    ==>#<Set: {:apple, :banana, :nectarine, :peach, :watermelon, :cantaloupe}>

There is no separate frozen set class, but, you can freeze a set. Example:

>> require 'set'
    ==>true
>> a = Set.new()
    ==>#<Set: {}>
>> a.add(:apricot)
    ==>#<Set: {:apricot}>
>> a.freeze
    ==>#<Set: {:apricot}>
>> a.add(:boysenberry)
RuntimeError: can't modify frozen Hash
        from /usr/lib/ruby/1.9.1/set.rb:228:in `[]='
        from /usr/lib/ruby/1.9.1/set.rb:228:in `add'
        from (irb):80
        from /usr/bin/irb:12:in `<main>'

4   Functions and Classes -- A Preview

Define a function with def/end. Parameters are optional. Use return to return a value, or list the value as the last value produced. For example, we can define a function in a file named tmp.rb:

def greater(a, b)
  if a < b
    return false
  elsif a > b
    return true
  end
  false
end

And, then we use it in irb as follows:

>> load './tmp.rb'
    ==>true
>> greater(3, 2)
    ==>true
>> greater(4,4)
    ==>false
>> greater(4,6)
    ==>false

5   Statements

5.1   Assignment

Ruby's assignment statement is very much like Python's.

You can even do unpacking. For example:

>> a = ["abc", 123]
    ==>["abc", 123]
>> b, c = a
    ==>["abc", 123]
>> b
    ==>"abc"
>> c
    ==>123

Ruby has "augmented" assignment operators:

x += y x = x + y
x -= y x = x - y
x /= y x = x / y
x *= y x = x * y
x %= y x = x % y
x **= y x = x ** y

Ruby can do parallel assignment and can swap values. Example:

>> # Parallel assignment.
>> a, b, c = :apricot, :banana, :cherry
    ==>[:apricot, :banana, :cherry]
>> puts a, b, c
apricot
banana
cherry
    ==>nil
>> # Swap the values in a and b.
>> a, b = b, a
    ==>[:banana, :apricot]
>> puts a, b, c
banana
apricot
cherry
    ==>nil

Note that the values on the right hand side (the Rvalue) are evaluated completely before the variables on the left hand side (the Lvalue) are modified.

5.2   require and require_relative

Load external modules that you want to use in your Ruby script with the require method. (require is a method in the Kernel module). Example -- Suppose file node_lib.rb contains this:

#!/usr/bin/env ruby

module NodeLib

class Node
    o
    o
    o

Then you can load it and use the Node class in it as follows:

require "node_lib"

node1 = NodeLib::Node.new(...)

Notes:

  • Reference items in a module using the module name, for example: ModuleName::Item.

  • Items define outside of a module block become global in the script that uses require.

  • load works like require, except that load forces the module to be loaded even if it has been previously loaded.

  • The list of directories to be searched for modules is in global variable $:. You can add additional directories with something like this:

    >> $:.push "."
    >> $:.push "/usr/home/myself/ruby_modules"
    

    Although, that is a questionable thing to do.

  • Better, in most situations, is to add the directory containing the code you want to reference with require to the RUBYLIB environment variable. See the section on Modules elsewhere in this document.

  • Use require_relative instead of require to ask Ruby to search for modules in directories relative to the location where execution is taking place.

  • require evaluates the code in a file. So if you have code in that file which you do not want to be evaluated when loaded with require, but you do want evaluated when the script is run, then hide that code inside this:

    if __FILE__ == $0
        # Code to be run when script is executed but not when "required".
        o
        o
        o
    end
    

5.3   puts and print

puts and print are not actually statements. They are functions in the Kernel module.

Use puts to display information. It's a method in Kernel.

puts automatically adds a newline. If you do not want the newline added, then use $stdout.write. Example:

$stdout.write "You say goodbye"
$stdout.write "I say hello"

Or, use print to write out text without a newline automatically added:

>> a = 3; puts "a: "; puts a
a:
3
    ==>nil
>> a = 3; print "a: "; puts a
a: 3
    ==>nil

5.4   if elif else

Here is a sample if statement:

def greater(a, b)
  if a < b
    return false
  elsif a > b
    return true
  else
    return false
  end
end

puts "3 < 4: #{greater(3, 4)}"
puts "4 < 3: #{greater(4, 3)}"
puts "4 < 4: #{greater(4, 4)}"

Running this file (if_statement.rb) produces the following:

$ ruby tmp.rb
3 < 4: false
4 < 3: true
4 < 4: false

Notes:

  • There can be multiple elsif clauses. They are all optional.

  • The else clause is optional.

  • The condition that follows the if and elsif keywords can be any expression.

  • The values false and nil count as false; every other value counts as true.

  • Add keyword then after the condition, if you want a statement following the condition on the same line as the condition. Example:

    >> name = "Dave"
        ==>"Dave"
    >> if name == "Dave" then puts "Hello" end
    Hello
        ==>nil
    

    But, usually, this is considered bad form.

  • A condition can be any expression, that is anything that returns a value.

  • The logical operators are not, and (or &&), and or (or ||).

  • Use parentheses to override the precedence of operators, or when you feel that parentheses help clarify the order of evaluation.

  • Parentheses can also be used to continue a long condition across multiple lines.

5.4.1   unless statement

An unless statement is like an if statement with the sense of the test reversed. You can think of "unless ..." being equivalent to "if not ...". Example:

>> flag = true
    ==>true
>> value = if flag then 100 else 0 end
    ==>100
>> value
    ==>100
>> flag = false
    ==>false
>> value = if flag then 100 else 0 end
    ==>0
>> value = unless flag then 100 else 0 end
    ==>100
>> value = if not flag then 100 else 0 end
    ==>100

5.4.2   True and false values in conditions

In a condition (for example, in an if, unless, and while statement), the values false and nil count as false; every other value counts as true. In other words, false and nil cause a condition to fail; everything else causes it to succeed.

5.4.3   if expression

The if statement is itself an expression. In Ruby, statements return a value and are, therefore, expressions.

However, when only the value is needed, you may find it more convenient to use an if expression. The form of an if expression is as follows:

<condition> ? <true-value> : <false-value>

Examples:

>> a = 4
    ==>4
>> b = 6
    ==>6
>> a < b ? -1 : +1
    ==>-1
>> b < a ? -1 : +1
    ==>1

5.4.4   if and unless modifier

You can also use an if expression as a modifier on a statement. Example:

>> flag = true
    ==>true
>> puts "hello" if flag
hello
    ==>nil
>> flag = false
    ==>false
>> puts "hello" if flag
    ==>nil
>> puts "hello" unless flag
hello
    ==>nil

5.4.5   Operators for conditions

You can learn about the precedence of all Ruby operators here: Ruby operator precedence.

Ruby operators (highest to lowest precedence) -- Operators with a "Yes" in the method column are actually methods, and as such may be overridden:

Method Operator Description
Yes [ ] [ ]= Element reference, element set
Yes ** Exponentiation (raise to the power)
Yes ! ~ + - Not, complement, unary plus and minus (method names for the last two are +@ and -@)
Yes * / % Multiply, divide, and modulo
Yes + - Addition and subtraction
Yes >> << Right and left bitwise shift
Yes & Bitwise AND
Yes ^ | Bitwise exclusive OR and regular OR
Yes <= < > >= Comparison operators
Yes <=> == === != =~ !~ Equality and pattern match operators (!= and !~ may not be defined as methods)
  && Logical AND
  || Logical OR
  Range (inclusive and exclusive)
  ? : Ternary if-then-else
  = %= { /= -= += |= &= >>= <<= *= &&= ||= **= Assignment
  defined? Check if specified symbol defined
  not Logical negation
  or and Logical composition
  if unless while until Expression modifiers
begin/end | Block expression

5.5   for statement

Here is an example of a for statement:

def for_statement
  container = [111, 222, 333]
  for item in container do
    puts "item: #{item}"
  end
end

for_statement

If this code is in file for_statement.rb and you run it, you will see:

$ ruby for_statement.rb
item: 111
item: 222
item: 333

Notes:

  • Advice -- Do not use the for statement. Look for a way to use an enumerator. Usually, this means looking for one of the methods in the each family, e.g. .each, .each_with_index, etc. For example:

    >> container = [111, 222, 333]
        ==>[111, 222, 333]
    >> container.each do |item|
    >>     puts "item: #{item}"
       end
    item: 111
    item: 222
    item: 333
        ==>[111, 222, 333]
    
  • The do keyword in the for statement is optional unless you want to put the statement in the block on the same line, which is bad form except in irb.

  • The for statement will also do unpacking. Example:

    >> for x, idx in container.each_with_index do puts "idx: #{idx}  x: #{x}" end
    idx: 0  x: 111
    idx: 1  x: 222
    idx: 2  x: 333
        ==>[111, 222, 333]
    

    But, again, the above is likely better re-written as:

    >> container.each_with_index do |x, idx| puts "idx: #{idx}  x: #{x}" end
    idx: 0  x: 111
    idx: 1  x: 222
    idx: 2  x: 333
        ==>[111, 222, 333]
    
  • Scope:

    • The variables defined and used in the block passed to an enumerator are local to the block.
    • The variables created by a for statement are local to the enclosing block.

5.6   while statement

The while statement repeats a block as long as a condition is true. Example:

def test
  idx = 0
  while idx < 4
    puts "idx: #{idx}"
    idx += 1
  end
end

test

If we put this in a file (e.g. while_statement.rb), and run it, we'll see:

$ ruby while_statement.rb
idx: 0
idx: 1
idx: 2
idx: 3

But while statements are often more clearly written using an enumerator. This example is roughly equivalent to the above while statement:

>> (0 .. 3).each { |item| puts "item: #{item}"  }
item: 0
item: 1
item: 2
item: 3
    ==>0..3

5.7   until statement

Ruby also had an until statement, which is similar to the while statement, except: (1) the keyword until is used in place of while; and (2) the sense of the condition is reversed (the block is evaluated while the condition is not true. This can sometimes simplify writing the condition.

Example:

def test3
  idx = 0
  until idx > 8
    idx += 1
    if idx.modulo(2) == 0
      next
    end
    puts "idx: #{idx}"
  end
end

5.8   break and next

The break statement exits the immediately enclosing loop. Example:

def test2
  (3 .. 10).each do |item|
    if item >= 7
      break
    end
    puts "item: #{item}"
  end
end

test2

If we put this in a file and run it, we'll see:

$ ruby tmp2.rb
item: 3
item: 4
item: 5
item: 6

The next statement starts execution at the top of the loop. Example:

def test1
  idx = 0
  while idx < 8
    idx += 1
    if idx.modulo(2) == 0
      next
    end
    puts "idx: #{idx}"
  end
end

test1

If we put this in a file and run it, we'll see:

$ ruby tmp2.rb
idx: 1
idx: 3
idx: 5
idx: 7

The redo statement starts execution at the top of the loop, but without evaluating the condition or fetching the next element.

5.9   Exceptions -- begin-rescue-end

Here is a sample script that:

  1. Defines several exception classes. The are subclasses class Exception.
  2. Implements a function (f1) that calls another funtion (f2) that raises an exception.
  3. Calls function f1 and handles the exception that might be raised.
class ModerateException < Exception
end

class ExtremeException < Exception
end

def f1 value
  f2 value
end

def f2 value
  case value
  when "bad"
    raise ModerateException, "things are bad"
  when "verybad"
    raise ExtremeException, "things are very bad"
  end
  return "every thing is ok"
end

def test arg1
  result = "undecided"
  begin
    result = f1 arg1
  rescue ModerateException => msg
    puts "handling moderate exception.  msg: \"#{msg}\""
  rescue ExtremeException => msg
    puts "handling extreme exception.  msg: \"#{msg}\""
  end
  puts "result: \"#{result}\""
end

if __FILE__ == $0
  if $*.length != 1
    $stderr.puts "usage: ruby exceptions.rb <label>"
    Kernel.exit(false)
  end
  label = $*[0]
  test(label)
end

Notes:

  • Our exception subclasses (ExtremeException and ModerateException) are empty. That's OK because they inherit any behavior we need from class Exception.
  • We catch either of those exception types in function test, and do something slightly different for each one.

Here is what we see when we run this script:

$ ruby exceptions.rb prettygood
result: "every thing is ok"
$ ruby exceptions.rb bad
handling moderate exception.  msg: "things are bad"
result: "undecided"
$ ruby exceptions.rb verybad
handling extreme exception.  msg: "things are very bad"
result: "undecided"

If we did not need to do something different for each different exception type (in this case, ModerateException and ExtremeException), then we could handle both types of exceptions in a single clause, and we could do so using either of the following techniques:

  1. We could reference both of our exception classes in a single rescue clause. Example:

    begin
      ...
    rescue ModerateException, ExtremeException => msg
      puts "handling some exception.  msg: \"#{msg}\""
    end
    
  2. Or, we could create a superclass of our exception classes and then "rescue" that:

    class GradedException < Exception
    end
    
    class ModerateException < GradedException
    end
    
    class ExtremeException < GradedException
    end
      o
      o
      o
    begin
      ...
    rescue GradedException => msg
      puts "handling some exception.  msg: \"#{msg}\""
    end
    

If we made this change to our code and then ran it, we'd see the following:

$ ruby exceptions.rb prettygood
result: "every thing is ok"
$ ruby exceptions.rb bad
handling some exception.  msg: "things are bad"
result: "undecided"
$ ruby exceptions.rb verybad
handling some exception.  msg: "things are very bad"
result: "undecided"

For clarity, here is a more complete program with separate "test" functions (test1, test2, and test3) for each of the above three cases:

class GradedException < Exception
end

class ModerateException < GradedException
end

class ExtremeException < GradedException
end

def f1 value
  f2 value
end

def f2 value
  case value
  when "bad"
    raise ModerateException, "things are bad"
  when "verybad"
    raise ExtremeException, "things are very bad"
  end
  return "every thing is ok"
end

#
# Handle exception types separately.
def test1 arg1
  result = "undecided"
  begin
    result = f1 arg1
  rescue ModerateException => msg
    puts "handling moderate exception.  msg: \"#{msg}\""
  rescue ExtremeException => msg
    puts "handling extreme exception.  msg: \"#{msg}\""
  end
  puts "result: \"#{result}\""
end

#
# Handle both exception types by catching them both explicitly.
def test2 arg1
  result = "undecided"
  begin
    result = f1 arg1
  rescue ModerateException, ExtremeException => msg
    puts "handling either exception explicitly.  msg: \"#{msg}\""
  end
  puts "result: \"#{result}\""
end

#
# Handle both exception types by catching their common supertype.
def test3 arg1
  result = "undecided"
  begin
    result = f1 arg1
  rescue GradedException => msg
    puts "handling either exception.  msg: \"#{msg}\""
  end
  puts "result: \"#{result}\""
end

if __FILE__ == $0
  if $*.length != 1
    $stderr.puts "usage: ruby exceptions.rb <label>"
    Kernel.exit(false)
  end
  label = $*[0]
  test3(label)
end

5.10   raise

raise is actually a function in the Kernel module.

See the sample code in the section Exceptions -- begin-rescue-end above.

Usually, you will want to raise an exception that is an instance of some built in, standard Ruby exception class or a subclass of class Exception that you define yourself.

When you raise an exception, create an instance of some exception class using the following form:

raise NameError, "the name is not defined"

When you catch an exception ("rescue" in Ruby terminology), specify the exception class you are interested in or one of its superclasses. Example:

class SampleException < Exception
end

def fn1
  puts "(fn1) One"
  raise NameError, "golly molly"
  puts "(fn1) Two"
end

def fn2
  puts "(fn2) One"
  raise SampleException, "holy moly"
  puts "(fn2) Two"
end

def test1
  begin
    fn1
  rescue NameError => exp
    puts "class: #{exp.class}"
    puts "msg: \"#{exp}\""
  end
  puts "-" * 40
  begin
    fn2
  rescue SampleException => exp
    puts "class: #{exp.class}"
    puts "msg: \"#{exp}\""
  end
end

test1

When run, the above prints out the following:

$ ruby exception_example.rb
(fn1) One
class: NameError
msg: "golly molly"
----------------------------------------
(fn2) One
class: SampleException
msg: "holy moly"

To find out the name of an appropriate exception class, you can "fake" the exception in irb. Example:

>> x = y
NameError: undefined local variable or method `y' for main:Object
        from (irb):34
        from /usr/bin/irb:12:in `<main>'
>> f = File.open("argle_bargle.txt", 'r')
Errno::ENOENT: No such file or directory - argle_bargle.txt
        from (irb):35:in `initialize'
        from (irb):35:in `open'
        from (irb):35
        from /usr/bin/irb:12:in `<main>'

From the above, we can learn (1) that NameError is the exception class used when a reference is made to an undefined variable and (2) that Errno::ENOENT is the exception class used when we try to open for reading a file that does not exist.

Here is an example that uses the above information to catch that specific File.open exception:

def test1
  missing_file_name = "some_missing_file.txt"
  begin
    infile = File.open(missing_file_name, 'r')
    puts infile.read
  rescue Errno::ENOENT => exp_obj
    puts "Error: #{exp_obj}"
    puts "file #{missing_file_name} is missing."
  end
end

test1

When we run the above, we see the following:

$ ruby file_exception.rb
Error: No such file or directory - some_missing_file.txt
file some_missing_file.txt is missing.

5.11   del statement

There is no del statement in Ruby. For some purposes, you can set a value to nil.

For arrays, use my_array.delete_at(index). For a hash (dictionary), use my_hash.delete(key).

5.12   case statement

Here is an example of a case statement:

case value
when "bad"
  puts "things are bad"
when "verybad"
  puts "things are very bad"
else
  puts "things are ok"
end

Notes:

  • the else clause is optional.

  • The case statement is an expression and it returns the last expression executed.

  • Add then after the condition if you want the when clauses on the same line. Example:

    >> id = "A002"
        ==>"A002"
    >> case id when "A001" then 5 else 1 end
        ==>1
    

6   Functions, Modules, and Packages

6.1   Functions

6.1.1   The def statement

The def statement is used to define functions and methods.

In Ruby, (1) the parameter list is not required; and (2) the block ends with an end keyword.

6.1.2   Returning values

Use the return statement. But, also, in Ruby, when the expression whose value is to be returned is the last expression evaluated in the function, then the return statement is optional.

A Ruby function, strictly speaking, can only return a single object. However, that object can be complex, which effectively enables a function to return multiple values. For a variable number of values, consider returning an Array. For a fixed number of "named" values, consider using a Struct, for example:

Tree = Struct.new(:name, :leaf)

def test1
  t1 = Tree.new
  t1.name = "pine"
  t1.leaf = "needle"
  t1
end

def test2
  t2 = Tree.new "oak", "flat"
  t2
end

def test
  value = test1
  puts "value.name: #{value.name}  value.leaf: #{value.leaf}"
  value = test2
  puts "value.name: #{value.name}  value.leaf: #{value.leaf}"
end

test

And, when we run the above, we see:

$ ruby struct_example.rb
value.name: pine  value.leaf: needle
value.name: oak  value.leaf: flat

6.1.3   Parameters and arguments

Keyword parameters are used in a function/method definition to give default values to a parameter. The default value is used when the argument is omitted in the function call. Here is an example of keyword parameters in a function definition:

>> def t(aa=4)
     puts "aa: #{aa}"
   end
    ==>nil
>> t
aa: 4
    ==>nil
>> t 8
aa: 8
    ==>nil

Newer versions of Ruby (version 2.0 and later) have support for keyword arguments. Here is an example containing a function with one normal argument and two keyword arguments:

#!/usr/bin/env ruby

def test(arg1, arg2:11, arg3:22)
  puts "#{arg1}. arg2: #{arg2}  arg3: #{arg3}"
end

def main
  test(1)
  test(2, arg2:33)
  test(3, arg3:44)
  test(4, arg2:55, arg3:66)
  # But, the following does not work
  # test(5, 77, 88)
end

if __FILE__ == $0
  main()
end

And, when we run the above example, we see:

$ ./test02.rb
1. arg2: 11  arg3: 22
2. arg2: 33  arg3: 22
3. arg2: 11  arg3: 44
4. arg2: 55  arg3: 66

See this for additional explanation: http://ruby-doc.com/docs/ProgrammingRuby/html/tut_methods.html (search for "Collecting Hash Arguments")

Earlier versions of Ruby (version 1.9 and earlier) do not have the ability to pass arguments to a function using keyword arguments. For example, the following does not work:

def complex_function(
  name="[no name]",
  url="http://www.noplace.com",
  category=nil,
  importance=-1
)
  puts "name: #{name}  " +
   "url: #{url}  " +
   "category: #{category}  " +
   "importance: #{importance}"
end

complex_function(url="http://www.someplace.com")

When run, this code produces the following, which is not correct:

$ ruby default_args_example.rb
name: http://www.someplace.com  url: http://www.noplace.com category:   importance: -1

What actually happens is that the value returned by the assignment expression is passed as the first (and only) argument to the function.

However, you can pass a Hash as an argument to a method. This is Ruby's version of keyword arguments. Here is an example:

def f1(vars)
  width = vars[:width]
  height = vars[:height]
  puts "width: #{width}"
  puts "height: #{height}"
  puts "area: #{width * height}"
end

f1(  { :width => 3, :height => 4 } )
puts "-----"
f1 :width => 5, :height => 6

When run, this script displays the following:

$ ruby keyword_args_example.rb
width: 3
height: 4
area: 12
-----
width: 5
height: 6
area: 30

6.1.4   Local and global variables

Variables that fit the form for local variables are local. See section Names and tokens for a table that gives the rules for naming variables.

In Ruby, global variables begin with an dollar sign ("$"). There is no need to use an equivalent of Python's global statement when assigning to a global variable inside a function. So, for example, the following function assigns a value to the global variable $total_expenses:

def update_expenses(expense)
  $total_expenses = expense
end

6.1.5   Doc strings for functions

The doc string for a function should be written as comment lines immediately before the def statement.

6.2   lambda, Proc, referencing functions, etc

Ruby supports a number of "functional programming" features.

Functions (actually, "methods" in Ruby) are not objects. In order to pass a method to a function or insert it in a data structure, we need to create a wrapper object for it. Example:

>> def f(x) puts "#{x}" end
    => nil
>> ref_f = method(:f)
    => #<Method: Object#f>
>> ref_f.call("abc")
abc
    => nil
>> ref_f.("abc")       # syntactic sugar for .call
abc
    => nil
>> ref_f["abc"]        # an alias for .call
abc
    => nil

Ruby's syntax for defining lambdas uses either "->" or "lambda". Example:

>> proc1 = -> x, y { puts "#{x} #{y}" }
    => #<Proc:0x00000001d83118@(irb):49 (lambda)>
>> proc1.call("aaa", "bbb")
aaa bbb
    => nil
>> proc2 = lambda { |x, y| puts "x: #{x}  y: #{y}" }
    ==>#<Proc:0x00000000c6c488@(irb):10 (lambda)>
>> proc2.call(:alpha, :beta)
x: alpha  y: beta
    ==>nil

Notes:

  • The "->" and "lambda" forms are equivalent. But, note the placement of the parameters outside the block when "->" is used.

  • Note the use of .call to call a Proc or lambda. We can alternatively use square brackets to call it (see example, below).

  • The "->" syntax defines a lambda, which is an instance of class Proc:

    >> proc1.class
        => Proc
    
  • A Proc is similar to a lambda, but with some differences (see below). We can define a Proc as follows:

    >> proc2 = Proc.new { |x, y| puts "#{x} #{y}"  }
        => #<Proc:0x00000001d117c0@(irb):71>
    >> proc2.call("cc", "dd")
    cc dd
        => nil
    

To use a Proc or lambda with an enumerator, enclose the Proc or lambda in a block. Example:

>> a = ['one', nil, 'two', nil, 'three']
    ==>["one", nil, "two", nil, "three"]
>> proc1 = lambda { |x| puts "x: #{x}" }
    ==>#<Proc:0x00000001182be8@(irb):26 (lambda)>
>> a.each { |x| if x.nil? then puts "it's nil" else proc1.call x end  }
x: one
it's nil
x: two
it's nil
x: three
    ==>["one", nil, "two", nil, "three"]

Or, if no additional logic is needed, you can pass the proc or lambda directly. Example:

>> a = ['one', nil, 'two', nil, 'three']
    ==>["one", nil, "two", nil, "three"]
>> proc2 = lambda { |x| if x.nil? then puts "it's nil" else puts "x: #{x}" end }
    ==>#<Proc:0x00000001193fd8@(irb):36 (lambda)>
>> a.each &proc2
x: one
it's nil
x: two
it's nil
x: three
    ==>["one", nil, "two", nil, "three"]

We can also call a Proc or lambda using square brackets. Example:

>> proc1['one']
hello
one
    ==>nil

There are some differences between a Proc (defined with Proc.new or proc) and a lambda (defined with -> or lambda), although both are instances of class Proc:

  • A lambda responds to the method call some_lambda.lambda? with true. A Proc responds with false.

  • A lambda is defined with -> or lambda. A Proc is defined with Proc.new or proc.

  • A lambda enforces its arity when it is called. If, for example, a lambda is defined to take two arguments, then when it is called, it must be passed exactly two arguments, else it throws an exception. A (plain) Proc does not do this.

  • When a "plain" Proc is called from within a method and it returns, it returns from itself and from the enclosing method. When a lambda returns, it returns only from itself (that is, from its own block). Example:

    def f
      puts "starting f"
      p1 = proc { return "abc" }
      puts p1.call
      puts "def"
    end
    
    def g
      puts "starting g"
      p1 = lambda { return "ghi" }
      puts p1.call
      puts "jkl"
    end
    
    def test
      f
      puts '-' * 10
      g
    end
    
    test
    

    When we run the above code, we see:

    $ ruby tmp4.rb
    starting f
    ----------
    starting g
    ghi
    jkl
    

    Think of it this way -- A Proc (created with Proc.new or proc) is like a block, and using a block is like copying that block into the current block. So, a Proc that contains a return statement, when called, returns from the current block, and not just from the itself.

It is also possible to get a method that can be called as follows:

>> b = []
    ==>[]
>> m1 = b.method :push
    ==>#<Method: Array#push>
>> m1.call 111
    ==>[111]
>> b
    ==>[111]
>> m1.call 222
    ==>[111, 222]
>> b
    ==>[111, 222]

Notes:

  • The method m1 is a bound method. In this case it is bound to the Array b.

In order to use a proc or lambda where a block is called for, you can pass the proc as the last (right most) argument to the method that requires it, prefixing the variable with &. Example:

>> p1 = lambda { |x| puts "xx: #{x}||#{x}" }
    ==>#<Proc:0x00000000931688@(irb):81 (lambda)>
>> ['aa', 'bb', 11, 22].each(&p1)
xx: aa||aa
xx: bb||bb
xx: 11||11
xx: 22||22
    ==>["aa", "bb", 11, 22]

Notes:

  • The ampersand (&) can be used to convert a Proc to a block or to convert a block to a Proc.

You can write a method that takes and uses a block with the yield operator. Example:

>> def f name
     yield name
   end
    ==>nil
>> f('dave') { |n| puts "my name is #{n}" }
my name is dave
    ==>nil

How do I know when to use a proc and when to use a lambda? Although you have some flexibility here, one guideline that may help is the following:

  1. When you want a value (i.e. you want your proc or lambda to return a value), use a lambda.
  2. When you want to execute your proc for its side-effects (and you are ignoring the value that it returns), use a (plain) proc.

You can also stuff a lambda or proc into a data structure. Example:

def g(notices, level)
  p = notices[level]
  p.call
end

def test
  notices = {
    'bad' => proc { puts "things are bad" },
    'fair' => proc { puts "things are fair" },
    'good' => proc { puts "things are good" },
  }
  g(notices, 'bad')
  g(notices, 'good')
end

test

Notes:

  • We create a Hash each of whose values is a proc.
  • We pass the Hash into a method which uses it to look up the proc to be used to display a notice.

And, when we run the above code, we see:

$ruby lambda_example1.rb
things are bad
things are good

6.3   Closures

proc and lambda (and their equivalents Proc.new and ->) can be used to create closures. A closure is a function that is closed upon (or that captures) the values of variables in its enclosing environment.

Example:

def makeproc(val)
  value = val.upcase
  fn = lambda { value + value }
  fn
end

def test
  value = 'art'
  fn1 = makeproc value
  value = 'bert'
  fn2 = makeproc value
  puts fn1.call
  puts fn2.call
  value = 'chuck'
  puts fn1.call
  puts fn2.call
  puts '------'
  value = 'dan'
  fn3 = lambda { value.upcase + value.upcase }
  puts fn3.call
  value = 'ed'
  puts fn3.call
end

test

When we run the above script, we see:

$ ruby closure_example.rb
ARTART
BERTBERT
ARTART
BERTBERT
------
DANDAN
EDED

Notice that fn1 and fn2 captured (or closed upon) the values of the variable in their environment. Hoever, fn3 enclosed upon the variable, so that, when we change the value of variable value, the value returned by fn3 changes, too.

More information on Ruby closures is here: http://technicalecstasy.net/closures-in-ruby-blocks-procs-and-lambdas/

6.4   Iterators and generators

Ruby makes heavy use of iterators. Iterators make writing loops easy; and the code can be quite clear. In many objects, you will find methods in the "each" family, for example obj.each, obj.each_with_index, etc. Those are almost always iterators. Also look for interators in Integer, Fixnum, and Range objects.

By the way, in Ruby, enumerator is another word for iterator.

An iterator, in Ruby is a method that takes a block of code as an argument and executes that block (usually multiple times).

6.5   Implementing enumerators/iterators

We can implement our own iterators. To do so, we implement a function that has a parameter preceeded by an "&" (ampersand). Here is an example:

def iter1(&block)
  3.times { |idx|
    block.call idx, "hi", "bye"
  }
end

iter1 { |idx, x, y| puts idx; puts x; puts y; puts "hello" }

Notes:

  • The "&" preceeding the parameter name tells Ruby to look for a block when we "call" that function.
  • Effectively, the ampersand tells Ruby to convert the block into a Proc.
  • When we call the block (use its .call method), we pass in 3 arguments. So, when we use our iterator (iter1), we pass it a block that takes 3 arguments.
  • Inside our iter1 function, block is a variable that has a first class object as a value, so we can do then things we normally can do with a first class object, e.g. stuff it in a structure, pass it to a function, and return it as the value of a function.
  • A block, in this case, is an instance of class Proc.

Here is one more example. In this one, the iterator takes one argument in addition to the block argument:

def iter2(count, &block)
  count.times { |idx|
    block.call idx, "hi", "bye"
  }
end

def test
  iter2(3) { |idx, x, y|
    puts "-----"
    puts idx
    puts x
    puts y
    puts "hello"
  }
end

test

When we run this, we see:

$ ruby iterator_example.rb
-----
0
hi
bye
hello
-----
1
hi
bye
hello
-----
2
hi
bye
hello

In the above examples, the ampersand (&) causes Ruby to convert a block into a Proc. You can also use ampersand to convert a Proc into a block, for example where a block is required by a method. Example:

>> proc1 = lambda { |x| puts "the x: #{x}" }
    ==>#<Proc:0x00000001e462f8@(irb):59 (lambda)>
>> numbers = [111, 222, 333]
    ==>[111, 222, 333]
>> numbers.each &proc1
the x: 111
the x: 222
the x: 333
    ==>[111, 222, 333]

Notes: In the above example, numbers is an Array. So, numbers.each requires a block (not a Proc). But, since we have a Proc (proc1), we convert that Proc into a block with the & (ampersand).

Here are a couple of examples that use yield instead of the & argument. With yield, you can "call" the block even though no explicit "block" parameter is given in the function definition. Example:

def run_it
  yield('andy', 1001)
  yield('betty', 1002)
end

def test
  run_it { |name, id| puts "name: #{name.ljust 16}  id: #{id}" }
end

test

When we run the above script, we should see:

$ ruby yield_example.rb
name: andy              id: 1001
name: betty             id: 1002

And, the following sample uses yield to implement something similar to an iterator. Example:

def iter3(count)
  count.times { |x|
    yield x * 2, x * 4
  }
end

def test3
  iter3(4) { |arg1, arg2| puts "arg1: #{arg1}  arg2: #{arg2}" }
end

test3

When we run this code, we'll see the following:

$ ruby iterator_example.rb
arg1: 0  arg2: 0
arg1: 2  arg2: 4
arg1: 4  arg2: 8
arg1: 6  arg2: 12

7   Classes

7.1   A simple class

Here is an example of a reasonably simple Ruby class:

class Node

  def initialize(value, children=[])
    @value = value
    @children = children
  end

  def value
    @value
  end

  def value=(newValue)
    @value = newValue
  end

  def children
    @children
  end

  def children=(newChildren)
    @children = newChildren
  end

  def add_child(newChild)
    @children.push(newChild)
  end

  def show(indent)
    puts "#{indent}value: \"#{@value}\""
    show_children(indent)
  end

  def show_children(indent)
    indent += "    "
    @children.each do |child|
      child.show(indent)
    end
  end

  def populate(level, maxdepth, maxwidth)
    if level < maxdepth
      (1..maxwidth).each do |idx1|
        node = Node.new("n-#{level}-#{idx1}")
        node.populate(level + 1, maxdepth, maxwidth)
        @children.push(node)
      end
    end
  end

end # end class Node

def test
  root = Node.new('root')
  root.populate(0, 3, 2)
  root.show('')
end

if __FILE__ == $0
  test
end

When we run the above sample, we see the following:

$ ruby class_example02.rb
value: "root"
    value: "n-0-1"
        value: "n-1-1"
            value: "n-2-1"
            value: "n-2-2"
        value: "n-1-2"
            value: "n-2-1"
            value: "n-2-2"
    value: "n-0-2"
        value: "n-1-1"
            value: "n-2-1"
            value: "n-2-2"
        value: "n-1-2"
            value: "n-2-1"
            value: "n-2-2"

Notes:

  • The initializer is called "initialize". This method will automatically be called when an instance is created.

  • Instance variables are marked with an "@" as the first character.

  • To create an instance of this class, use:

    >> new_node = Node.new("some label")
    
  • Here is an example that creates several node objects and shows them:

    nc1 = Node.new("c1")
    nc2 = Node.new("c2")
    nb1 = Node.new("b1", [nc1, nc2])
    nb1.show("")
    

7.2   Defining methods

Define methods for a class with the def keyword. Within a method, reference instance variables using the "@" leading character.

7.3   The constructor

The constructor is a method that is called to initialize a instance of a class when that instance is created. In Ruby, the name of the ctor (constructor) is "initialize".

The constructor is a good place to initialize all the instance variables for the class.

7.4   Member variables

Member variables (instance variables) are named with a leading "@".

By default, member variables are hidden. They cannot be accessed from outside the class or its subclasses. Here is an example of a pair of getter and setter methods that give access to a member variable:

def value
  @value
end

def value=(newValue)
  @value = newValue
end

Notes:

  • Naming a method with a trailing equal sign ("=") enables us to call that method with the assignment operator, as follows:

    >> my_node.value = "abcd"
    
  • And, the other method in this example enables us to get the value:

    >> value = my_node.value
    

But, if our getters and setters do nothing more than get and set the values of an instance variable, then we can more concisely define getters and setters using:

  • attr_reader -- Create read-only instance variables.
  • attr_writer -- Create write-only instance variables.
  • attr_accessor -- Create readable and writable instance variables.

Example:

class MyClass
  def initialize
      o
      o
      o
  end
  attr_reader :size, :color
  attr_writer :description
  attr_accessor :name, :phone

Notes:

  • size and color will be read-only from outside of the class. That means that we can write:

    x = obj.size
    

    But, not:

    obj.size = x
    
  • description will be write-only from outside of the class.

  • name and phone will be both readable and writable from outside of the class. We can both get and set them. Example:

    obj.name = "Dave"
    puts obj.name       # prints "Dave"
    

7.5   Calling methods

Call methods in an instance the way you normally would, either with or without parentheses. Examples:

>> my_node.show "hello"
>> my_node.show("hello")

7.6   Adding inheritance

Inheritance enables us to reuse capabilities from existing classes.

Here is an example:

class ColorNode < Node

  def initialize(value, color, children=nil)
    super(value, children)
    @color = color
  end

  def color
    @color
  end

  def color=(newColor)
    @color = newColor
  end

  def show(indent)
    puts "#{indent}value: \"#{@value}\"  color: #{@color}"
    show_children(indent)
  end

end # end class ColorNode

Notes:

  • Be sure to notice that the first thing in our constructor (method initialize) we call the constructor in the superclass.
  • Class ColorNode has all the methods from class Node (see definition in example above) plus the methods defined in class ColorNode itself.
  • In class ColorNode we override method show. So, when show is called on an instance of ColorNode, the definition in ColorNode is used. When show is called on an instance of class Node, the definition in that class is used.

Ruby supports single inheritance. Each class can inherit from only one class. Ruby does not support multiple inheritance. However, you can get some of the features of multiple inheritance by using mixins. For more on mixins, see Mixins and the include command in this document.

If you do not specify a superclass for one of your classes, then the default superclass is class Object.

An instance of a subclass has all the methods defined in the superclass plus the methods defined in the subclass.

When a method in a subclass has the same name as a method in a superclass, we say that the method in the subclass "overrides" the method of the same name in the superclass. When we call that method on an instance of the subclass, the definition of the method in the subclass is used. When we call that method on an instance of the superclass, the definition of the method in the superclass is used.

A helpful rule for reasoning about method resolution: Ruby searches for a method by starting at the class of which the object is an instance and uses the first method of that name found while working upward in the class hierarchy.

7.7   Class variables

Mark class variables with an initial "@@".

Here is a sample class that has both a class variable and a class method. Example:

class Node

  @@description = ""

  def initialize(name, children=nil)
    @name = name
    if children.nil?
      @children = []
    else
      @children = children
    end
  end

  def Node.description
    @@description
  end

  def Node.description= newDescription
    @@description = newDescription
  end

  def show
    puts "description: \"#{@@description}\""
    puts "    name: \"#{@name}\"  children: #{@children}"
  end

end

def test
  node1 = Node.new 'FirstNode'
  Node.description = "Some nodes"
  puts Node.description
  Node.description = "More nodes"
  node1.show
end

test

When we run this, we see the following:

$ ruby class_method_example.rb
Some nodes
description: "More nodes"
    name: "FirstNode"  children: []

Notes:

  • The class variable "@@description" is marked with a double "@".
  • The class method "Node.description" is prefixed with the class name and a dot.
  • We can reference class variables from both class methods and from instance methods.

7.8   Class methods

A class method can be called without an instance of the class, in contrast with more "normal" instance methods which require a instance in order to be called.

In order to understand how to define a class method, we need to under stand that the following code:

class SomeClass
  class << some_object
    def a_class_method
      ...
    end
  end
end

defines a method a_class_method in the scope of the eigen class of some_object.

So, the following code:

class SomeClass
  class << self
    def a_class_method
      ...
    end
  end
end

defines a method a_class_method in the scope of the eigen class of the object itself. In other words, a_class_method becomes a class method that we can call using the class rather than an instance. We can call it as follows:

SomeClass.a_class_method

Here is our previous Node and tree example, with the populate method converted into a class method:

#!/usr/bin/env ruby

class Node

  def initialize(value, children=[])
    @value = value
    @children = children
  end

  def value
    @value
  end

  def value=(newValue)
    @value = newValue
  end

  def children
    @children
  end

  def children=(newChildren)
    @children = newChildren
  end

  def add_child(newChild)
    @children.push(newChild)
  end

  def show(indent)
    puts "#{indent}value: \"#{@value}\""
    show_children(indent)
  end

  def show_children(indent)
    indent += "    "
    @children.each do |child|
      child.show(indent)
    end
  end

  class << self
    #
    # class method -- Populate a tree.  Return the root of the tree.
    def populate(name, maxdepth, maxwidth)
      root = Node.new(name)
      root.fill_tree(0, maxdepth, maxwidth)
      root
    end
  end

  def fill_tree(level, maxdepth, maxwidth)
    if level < maxdepth
      (1..maxwidth).each do |idx1|
        node = Node.new("n-#{level}-#{idx1}")
        node.fill_tree(level + 1, maxdepth, maxwidth)
        @children.push(node)
      end
    end
  end

end # end class Node

def test
  root = Node.populate('root', 3, 2)
  root.show('')
end

if __FILE__ == $0
  test
end

Notes:

  • We use the idiom class << self ... end to open up the scope of the class so that we can define class methods (as opposed to instance methods) in that scope.

  • When we call method populate, we use the class rather than an instance, for example: Node.populate(...).

  • A class method can alternatively be defined as follows -- class method style 2:

    class Node
        ...
        def self.populate(rootname, prefix, maxdepth, maxwidth)
          root = Node.new(rootname)
          root.fill_tree(prefix, 0, maxdepth, maxwidth)
          root
        end
        ...
    end
    
  • Or, even as follows -- class method style 3:

    class Node
        ...
    end
    
    def Node.populate(rootname, prefix, maxdepth, maxwidth)
      root = Node.new(rootname)
      root.fill_tree(prefix, 0, maxdepth, maxwidth)
      root
    end
    

A bit of helpful information about class methods is here: http://www.railstips.org/blog/archives/2009/05/11/class-and-instance-methods-in-ruby/

7.9   Extending an existing class

We can extend an existing class. This is like defining a subclass, except that our extension applies to instances created from the existing class.

Example:

class Fixnum
  def double
    self * 2
  end
end

def test
  a = 5
  b = a.double
  puts "a: #{a}  b: #{b}"
end

test

When we run the above, we see:

$ ruby extend_class_example.rb
a: 5  b: 10

This is sometimes called "monkey patching".

Caution: Using this capability enables you to change the behavior of existing classes, even builtin classes, and therefore, it may cause unexpected behavior in other sections of code. It should be used with care. Here is an example of this kind of devious behavior in which one file changes the behavior of the String class when used in a different file. This file makes the evil change to the String class:

# File: monkey_patch_example.rb

require './monkey_patch'

class String

  # Questionable -- Does not change expected behavior of existing methods.
  def show_length
    puts self.length
  end

  # Definitely evil -- Changes the expected behavior of the String class.
  def length
    25
  end

end

def test
  MonkeyPatch::test_string_methods
end

test

And, this file is the unsuspecting victim of that change:

# File: monkey_patch.rb

module MonkeyPatch

  def MonkeyPatch.test_string_methods
    s1 = "abcd"
    puts "length of s1: #{s1.length}"
    s1.show_length
  end

end

When we run the above code, we are (not to) surprised to see the following:

$ ruby monkey_patch_example.rb
length of s1: 25
25

7.10   Mixins and the include command

Here is another example where there is more dependence of the modules upon the internals of the class that includes them. Example:

#!/usr/bin/env ruby

module A
  def a1
    puts "a1 is called for object name: #{@name}"
  end
end

module B
  def b1
    puts "b1 is called for object name: #{@name}"
  end
end

module C
  def c1
    puts "c1 is called for object name: #{@name}"
  end
end

class Test
  def initialize name
    @name = name
  end
  attr_accessor :name
  include A
  include B
  include C

  def display
    puts 'Modules are included'
  end

  def show
    puts "showing #{@name}"
    a1
    b1
    c1
  end

end

def test
  object=Test.new :oregano
  object.display
  object.a1
  object.b1
  object.c1
  puts '-----'
  object.show
end

def main
  if ARGV.length != 0
    $stderr.write "usage: module_example3.rb\n"
    exit
  end
  test
end

if __FILE__ == $0
  main
end

When we run the above script, we see the following:

$ ./module_example3.rb
Modules are included
a1 is called for object name: oregano
b1 is called for object name: oregano
c1 is called for object name: oregano
-----
showing oregano
a1 is called for object name: oregano
b1 is called for object name: oregano
c1 is called for object name: oregano

Notes:

  • The include command effectively copies the code that is inside the included module and inserts it at the location of the include command.
  • This is Ruby's way of allowing us to use mixins.
  • Here are several possible uses for the include command: (1) Code reuse: copy the same code into multiple classes, but enable you to maintain and modify a single copy of that code. (2) Customization I: enable a customer or client (an external user) to customize your class by adding specific methods (whose interface you specify) to your class when they run that code. (3) Customization II: enable multiple uses of your class in different parts of the same application but customised with different versions of a mixin module.
  • Notice how the mixins in the above sample code (methods a1, b1, and c1) know something about the class that they are inserted into. Methods a1, b1, and c1 "know" about instance variable name. That's either bad design, or it needs to be clearly stated and documented. Perhaps something like this would help: "Mixins for this class have access to a instance variable "name", a string. But, remember, if and when you do this, the existence of a mixin module that relies on internals of your class will restrict future changes to your class.

In order to add class methods to a class, use extend instead of include.

All objects also support the .extend method, which can be used to add methods from one or more modules to an instance. Example:

>> module ModA
   def hello
     puts "hello"
   end
   end
    ==>nil
>> a = 'abc'
    ==>"abc"
>> a.hello
NoMethodError: undefined method `hello' for "abc":String
        from (irb):67
        from /usr/bin/irb:12:in `<main>'
>> a.extend(ModA)
    ==>"abc"
>> a.hello
hello
    ==>nil

7.11   Refinement -- refining an existing class

Caution: Refinement requires Ruby version 2.0 or later. It is currently experimental and subject to change.

We can extend an existing class is a way such that the extension is visible only within a specific context (that is, a specific scope within your code). In Ruby, this is called "refinement".

How to do it:

  • Define a refinement with the refine method.
  • Use a refinement with the using method.

Here is an example of (1) a module that defines a refinement and (2) the use of that refinement:

#
# Define a refinement.
module Doubler
  refine String do
    def double
      self * 2
    end
  end
end

#
# Use the refinement defined in module Doubler.
class Foo
  using Doubler

  def test_double
    str1 = "abcd"
    str2 = str1.double
    puts %[str1: "#{str1} str2: "#{str2}"]
  end
end

def test
  foo1 = Foo.new
  foo1.test_double
end

def broken_code
  str1 = "abcd"
  str2 = str1.double
  puts %[str1: "#{str1} str2: "#{str2}"]
end

test
puts '-----'
broken_code

When we run the above code, we might see:

$ ruby refinement_example.rb
refinement_example.rb:14: warning: Refinements are experimental,
and the behavior may change in future versions of Ruby!
str1: "abcd str2: "abcdabcd"
-----
refinement_example.rb:40:in `broken_code': undefined method `double'
for "abcd":String (NoMethodError)
        from refinement_example.rb:46:in `<main>'

Notes:

  • Note the warning. This feature is available in recent versions of Ruby only. It is subject to change. Use at your own risk.
  • The same code that works inside class Foo which is using the Doubler module, does not work in method broken_code, where we are not using the Doubler module.

7.12   Doc strings for classes

The doc string for a class should be written as comment lines immediately before the class statement.

8   Modules

From the "Ruby User's Guide (http://www.rubyist.net/~slagell/ruby/modules.html):

"Modules in ruby are similar to classes, except:

  • A module can have no instances.
  • A module can have no subclasses.
  • A module is defined by module ... end."

Modules help solve problems with name conflicts within a single file. Different modules effectively define separate namespaces. Example:

#!/usr/bin/env ruby

module ModuleA

  $global_var1 = 'abcd'
  @@module_global_var1 = 'efgh'

  def ModuleA.message1
    puts "Hello from ModuleA::message1"
    puts "(ModuleA::message1) module_global_var1: #{@@module_global_var1}"
    @@module_global_var1 = 'ijkl'
    true
  end

  def ModuleA.message2
    puts "Hello from ModuleA::message2"
    puts "(ModuleA::message2) module_global_var1: #{@@module_global_var1}"
    true
  end
end

module ModuleB
  def ModuleB.message
    puts "Hello from ModuleB::message"
    puts "(ModuleB::message) global_var1: #{$global_var1}"
    false
  end
end

def main
  if ARGV.length != 0
    $stderr.write "usage: module_example1.rb\n"
    exit
  end
  ModuleA::message1
  ModuleA::message2
  ModuleB::message
end

if __FILE__ == $0
  main
end

Notes:

Of course, we also want to be able to use our modules from another Ruby source code file. The following code does that. Example:

#!/usr/bin/env ruby

require_relative 'module_example1'

def main
  if ARGV.length != 0
    $stderr.write "usage: module_example2.rb\n"
    exit
  end
  ModuleA::message1
  ModuleA::message2
  ModuleB::message
end

if __FILE__ == $0
  main
end

When we run the above code, we'll see the following:

$ ./module_example2.rb
Hello from ModuleA::message1
(ModuleA::message1) module_global_var1: efgh
Hello from ModuleA::message2
(ModuleA::message2) module_global_var1: ijkl
Hello from ModuleB::message
(ModuleB::message) global_var1: abcd

Notes:

8.1   The include command

The include command effectively copies code from a module into the current namespace. Example:

>> Math.sin(3.14)
    ==>0.0015926529164868282
>> sin(3.14)
NoMethodError: undefined method `sin' for main:Object
        from (irb):3
        from /usr/bin/irb:12:in `<main>'
>> include Math
    ==>Object
>> sin(3.14)
    ==>0.0015926529164868282

Notes:

  • You can use the include command to avoid having to repeatedly use a module qualifier, for example: Math.sin(3.14).

8.2   Mixins using modules and include

We can use modules and classes to implement mixins, which gives us a controlled and limited form of multiple inheritance. The include effectively inserts methods into a namespace. So, if we include a module inside a class, doing so is the same as copying those methods into the class. Here is a simple example:

#!/usr/bin/env ruby

$debug = true

module Util
  def dbgprint msg
    if $debug
      puts "Debug <#{@name}> #{msg}"
    end
  end
end

class Tree
  def initialize name, region
    @name = name
    @region = region
  end

  include Util

  def describe
    dbgprint "Describing"
    puts "Tree: #{@name}  region: #{@region}"
  end
end

class Bush
  def initialize name, zone
    @name = name
    @zone = zone
  end

  include Util

  def describe
    dbgprint "Describing"
    puts "Bush: #{@name}  zone: #{@zone}"
  end
end

def main
  t1 = Tree.new "jeffery pine", "northwest U.S."
  b1 = Bush.new "manzanita", "chaparral"
  plants = [t1, b1]
  plants.each do |plant|
    plant.describe
  end
end

if __FILE__ == $0
  main
end

Notes:

  • The dbgprint method from the Util module is copied into both the Tree class and the Bush class.
  • Since the dbgprint method is inserted inside these two classes and since both these classes have a @name instance variable, the dbgprint method can reference that variable.

When we run the above code, we see the following:

$ ./module_example3.rb
Debug <jeffery pine> Describing
Tree: jeffery pine  region: northwest U.S.
Debug <manzanita> Describing
Bush: manzanita  zone: chaparral

8.3   Doc strings for modules

The doc string for a module should be written as comment lines immediately before the module statement.

9   Packages

10   Practical and special tasks

10.1   Command line options and arguments

We can use the OptionParser class to parse command line options and arguments. Information about that class is here: http://ruby-doc.org/stdlib-2.1.2/libdoc/optparse/rdoc/OptionParser.html. And, if the Ruby information system is installed on your machine, you can type:

$ ri OptionParser
$ ri OptionParser.parse!

etc.

Here is a sample program that captures two command line options and one command line arguement:

#!/usr/bin/env ruby

require 'optparse'
require 'ostruct'

$Usage = <<END
Synopsis:
    Add reST (reStructuredText) heading decoration to a line.
Usage:
    underline.rb [options] char
Arguments:
    char        the character to be used for decoration
Options:
END

def underline(chr1, options)
  line = $stdin.read
  line = line.rstrip
  decoration = chr1 * (line.length + options.extra)
  if options.over_under
    content = "#{decoration}\n#{line}\n#{decoration}\n"
  else
    content = "#{line}\n#{decoration}\n"
  end
  $stdout.write(content)
end

def main
  options = OpenStruct.new
  options.over_under = false
  options.extra = 0
  optparser = OptionParser.new do |opts|
    opts.banner = $Usage
    opts.on('-o', '--[no-]over-under', 'decorate both over and under') do |arg|
      options.over_under = arg
    end
    opts.on("-x", "--extra [N]", Integer,
            'add N extra characters at end') do |arg|
      options.extra = arg
    end
    opts.separator "    ----"
    opts.on_tail("-h", "--help", "show this message") do
      puts opts
      exit
    end
  end
  optparser.parse!
  if $*.length != 1
    puts optparser
    Kernel.exit false
  end
  chr1 = $*[0]
  if chr1.length != 1
    puts optparser
    Kernel.exit false
  end
  underline(chr1, options)
end

if __FILE__ == $0
  main()
end

If we run the above code with the --help option, we'll see:

$ underline.rb --help
Synopsis:
    Add reST (reStructuredText) heading decoration to a line.
Usage:
    underline.rb [options] char
Arguments:
    char        the character to be used for decoration
Options:
    -o, --[no-]over-under            decorate both over and under
    -x, --extra [N]                  add N extra characters at end
    ----
    -h, --help                       show this message

Notes:

  • Notice our use of the require statement. We use an OptionParser (optparse) object to parse command line options. We use and OpenStruct (ostruct) object to save the options.
  • Option -o/--[no-]over-under is a boolean. It's true or false. Notice the "[no-]" part. That enables the user to use the option "--no-over-under" in order to set it to false (which in this case happens to be the default).
  • Option -x/--extra [N] takes an integer argument. Notice the "[N]" in the specification. And, because we specified the desired type (Integer), the value will be converted to that type for us automatically.
  • Because we set the banner attribute, we can print out usage information, which also describes the options, simply by "formatting" the OptionParser object. Also see method OptionParser.separator, which can be used to add text between the display of various options, as in the above example.
  • The parse! method, in contrast with the parse method, removes the options that it parses from ARGV and $*, so that we are only left with the command line arguments.

10.2   XML and libxml

"The Libxml-Ruby project provides Ruby language bindings for the GNOME Libxml2 XML toolkit."

Libxml-Ruby can be installed as a Ruby Gem.

More information is here:

10.2.1   Parse a file

>> require 'libxml'
>> document = LibXML::XML::Document.file("people.xml")

10.2.2   Get the root element

>> root = document.root

10.2.3   Search for elements with xpath

>> name_nodes = root.find ".//name"
    ==>#<LibXML::XML::XPath::Object:0x00000001278278>
>> name_nodes = root.find(".//name").entries
    ==>[<name>Alberta</name>, <name>Bernardo</name>, ...
>> name_node1 = name_nodes[0]
    ==><name>Alberta</name>
>> agent_node = root.find(".//agent").entries[-1]
    ==><agent>
            <firstname>Harvey</firstname>
            <lastname>Hippolite</lastname>
            <priority>5.2</priority>
            <info name="Harvey Hippolite" type="543" rating="6.55"/>
        </agent>

10.2.4   Determine the type of node

>> agent_node.node_type == LibXML::XML::Node::ELEMENT_NODE
    ==>true
>> puts agent_node.node_type_name
element
    ==>nil
>> agent_node.node_type == LibXML::XML::Node::TEXT_NODE
    ==>false
>> name_node1
    ==><name>Alberta</name>
>> name_node1.text?
    ==>false
>> name_node1.element?
    ==>true
>> name_node1.entries
    ==>[Alberta]
>> name_node1.entries[0]
    ==>Alberta
>> name_node1.entries[0].text?
    ==>true

10.2.5   Get the attributes, children, and text from a node

Iterate over each child node:

>> agent_node.each_element { |node| puts node }
<firstname>Harvey</firstname>
<lastname>Hippolite</lastname>
<priority>5.2</priority>
<info name="Harvey Hippolite" type="543" rating="6.55"/>
    ==>nil

Iterate over all child nodes, including text and element nodes:

>> agent_node.each { |node| puts node }


<firstname>Harvey</firstname>


<lastname>Hippolite</lastname>


<priority>5.2</priority>


<info name="Harvey Hippolite" type="543" rating="6.55"/>


    ==>nil

Get the attributes of an element -- First, use xpath to get an element:

>> person1 = root.find(".//person").entries[0]
    ==><person id="1" value="abcd" ratio="3.2" extraattr="aaa">
        <name>Alberta</name>
        <interest>gardening</interest>
        <interest>reading</interest>
        <category>5</category>
        <hot/>
        <hot.agent oneattr="bbb" twoattr="ccc">
            <firstname>Paula</firstname>
            <lastname>Peterson</lastname>
            <priority>4.3</priority>
        </hot.agent>
    </person>

Iterate over each of the attributes of the element:

>> person1.each_attr { |attr| puts "attr -- name: #{attr.name}  value: #{attr.value}" }
attr -- name: id  value: 1
attr -- name: value  value: abcd
attr -- name: ratio  value: 3.2
attr -- name: extraattr  value: aaa
    ==>#<LibXML::XML::Attributes:0x000000010e4330>
>> person1.attributes.each_with_index { |attr, idx| puts idx, attr.class, attr }
0
LibXML::XML::Attr
id = 1
1
LibXML::XML::Attr
value = abcd
2
LibXML::XML::Attr
ratio = 3.2
3
LibXML::XML::Attr
extraattr = aaa
    ==>#<LibXML::XML::Attributes:0x000000013f7b80>
>> person1.attributes.class
    ==>LibXML::XML::Attributes

Access a few attributes individually:

>> person1.attributes
    ==>#<LibXML::XML::Attributes:0x0000000111b470>
>> person1.attributes.entries
    ==>[id = 1, value = abcd, ratio = 3.2, extraattr = aaa]
>> person1.attributes.entries[0]
    ==>id = 1
>> person1.attributes.entries[0].name
    ==>"id"
>> person1.attributes.entries[0].value
    ==>"1"
>> person1['ratio']
    ==>"3.2"
>> person1['ratio'] = "5.6"
    ==>"5.6"
>> person1['ratio']
    ==>"5.6"
>> person1.attributes['ratio']
    ==>"5.6"
>> person1.attributes['ratio'] = "7.8"
    ==>"7.8"
>> person1.attributes['ratio']
    ==>"7.8"
>> person1['ratio']
    ==>"7.8"

10.2.6   Walk the tree of elements

Here is a sample script that parses an XML document, walks the tree of nodes/elements, and prints out a bit of information about each node.

require 'libxml'

def walk(node, level, proc_node)
  proc_node.call(node, level, proc_node)
  node.each do |child|
    walk child, level + "    ", proc_node
  end
end

$proc_node_1 = Proc.new { |node, level|
  case node.node_type
  when LibXML::XML::Node::TEXT_NODE
    if node.content.strip.empty?
      puts "#{level}Empty text node"
    else
      puts "#{level}Text node: \"#{node.content}\""
    end
  when LibXML::XML::Node::ELEMENT_NODE
    puts "#{level}Element node -- tag #{node.name}"
    node.each_attr { |attr|
      puts "#{level}    Attr -- name: #{attr.name}  value: \"#{attr.value}\""
    }
  else
    puts "#{level}Unknown node type"
  end
}

def test(infilename)
  doc = LibXML::XML::Document.file(infilename)
  root = doc.root
  walk root, "", $proc_node_1
end

def main
  if $*.length != 1
    $stderr.puts $Usage
    Kernel.exit(false)
  end
  infilename = $*[0]
  test(infilename)
end

if __FILE__ == $0
  main()
end

Notes:

  • The walk method is the one that traverses the tree of elements.
  • The walk method receives a Proc that it uses to process the current node. Doing so would enable us to reuse the same traversal method (walk), but do different things to each node.

When we run this example, we might see something like this:

Element node -- tag: people
    o
    o
    o
    Element node -- tag: person
        Attr -- name: id  value: "1"
        Attr -- name: value  value: "abcd"
        Attr -- name: ratio  value: "3.2"
        Attr -- name: extraattr  value: "aaa"
        Empty text node
        Element node -- tag: name
            Text node: "Alberta"
        Empty text node
            o
            o
            o

10.2.7   Modify an element

Change the value of an attribute:

>> person1 = root.find(".//person").entries[0]
    ==><person id="1" value="xyz" ratio="3.2" extraattr="aaa">
        <name>Alberta</name>
        <interest>gardening</interest>
        <interest>reading</interest>
        <category>5</category>
        <hot/>
        <hot.agent oneattr="bbb" twoattr="ccc">
            <firstname>Paula</firstname>
            <lastname>Peterson</lastname>
            <priority>4.3</priority>
        </hot.agent>
    </person>
>> person1.attributes.entries[1]
    ==>value = xyz
>> person1.attributes.entries[1].value = "12345"
    ==>"12345"
>> person1.attributes.entries[1]
    ==>value = 12345

Change the text in a text node:

>> name_node = root.find(".//name").entries[0]
    ==><name>Alberta</name>
>> name_node
    ==><name>Alberta</name>
>> name_node.content
    ==>"Alberta"
>> name_node.content = "Bertrand"
    ==>"Bertrand"
>> name_node.content
    ==>"Bertrand"

Create a new node and add an attribute to it. Then add the new node as a child of an existing node:

>> new_node = LibXML::XML::Node.new("aaa")
    ==><aaa/>
>> attr = LibXML::XML::Attr.new(new_node, "size", "25")
    ==>size = 25
>> new_node
    ==><aaa size="25"/>
>> person1 << new_node
    ==><person id="1" value="12345" ratio="3.2" extraattr="aaa">
        <name>Bertrand</name>
        <interest>gardening</interest>
        <interest>reading</interest>
        <category>5</category>
        <hot/>
        <hot.agent oneattr="bbb" twoattr="ccc">
            <firstname>Paula</firstname>
            <lastname>Peterson</lastname>
            <priority>4.3</priority>
        </hot.agent>
    <aaa size="25"/></person>

10.3   Using a relational database

Here is a simple example that opens a connection to a SQLite3 database, executes a SQL query, displays the results from the query (a rowset), and then closes the connection. In this example we use the Ruby adapter for sqlite3, which is a light-weight, file based relational database:

>> require 'sqlite3'
>> db = SQLite3::Database.open("mysite.db")
    ==>#<SQLite3::Database:0x000000013bc198>
>> result = db.execute "select * from polls_choice"
    ==>[[1, 1, "Not much", 0], [2, 1, "Something or other", 0], [3, 1, "Random acts of comedy", 0], [4, 1, "Gardening", 0], [5, 1, "Bird watching", 0], [6, 2, "Programming", 0], [7, 2, "Writing", 0], [8, 2, "Walking", 0]]
>> result.class
    ==>Array
>> result.each { |x| print x; puts }
[1, 1, "Not much", 0]
[2, 1, "Something or other", 0]
[3, 1, "Random acts of comedy", 0]
[4, 1, "Gardening", 0]
[5, 1, "Bird watching", 0]
[6, 2, "Programming", 0]
[7, 2, "Writing", 0]
[8, 2, "Walking", 0]
    ==>[[1, 1, "Not much", 0], [2, 1, "Something or other", 0], [3, 1, "Random acts of comedy", 0], [4, 1, "Gardening", 0], [5, 1, "Bird watching", 0], [6, 2, "Programming", 0], [7, 2, "Writing", 0], [8, 2, "Walking", 0]]
>> db.close
    ==>#<SQLite3::Database:0x000000013bc198>

Instead of retrieving a rowset, we can execute a query and treat it as an enumerator:

>> db.execute("select * from polls_choice") { |row| print row; puts }
[1, 1, "Not much", 0]
[2, 1, "Something or other", 0]
[3, 1, "Random acts of comedy", 0]
[4, 1, "Gardening", 0]
[5, 1, "Bird watching", 0]
[6, 2, "Programming", 0]
[7, 2, "Writing", 0]
[8, 2, "Walking", 0]
    ==>#<SQLite3::Statement:0x0000000122f6b8>

Here are links to help with ruby-sqlite3:

10.4   Analytics, numerics, big data, statistics, etc

Here are some of the packages that you may want to look at:

  • statsample
  • nmatrix
  • sciruby

10.7   Debugging ruby scripts

You can use the Ruby debug module to debug your scripts with the interactive, command oriented debugger. Start it up with one of the following:

$ ruby -rdebug my_scipt.rb

Or, add the following to your script at the location where you want to drop into the debugger:

require 'debug'

The first method is likely preferred, because using the second method leaves some debugging capabilities disabled, for example, the l(list) command does not seem to work. If you must use the second method (require 'debug'), then once in the debugger, you might try to "fix things up" by running the following commands:

(rdb) trace on
(rdb) next
(rdb) trace off

Once you are in the debugger, type "help" at the debugger prompt in order to get help with the debugger and its commands:

(rdb) help

10.8   Benchmarking Ruby code

An easy way is to use the Benchmark module, which comes with the Ruby distribution.

The comments at the top of that module provide examples the show how to use it. If the Ruby information system is installed, you can also see those examples by typing the following at the command line:

$ ri Benchmark

I don't think I can make it much clearer, so I've reprinted those comments and examples here:

# The Benchmark module provides methods for benchmarking Ruby code, giving
# detailed reports on the time taken for each task.
#

# The Benchmark module provides methods to measure and report the time
# used to execute Ruby code.
#
# * Measure the time to construct the string given by the expression
#   <code>"a"*1_000_000_000</code>:
#
#       require 'benchmark'
#
#       puts Benchmark.measure { "a"*1_000_000_000 }
#
#   On my machine (OSX 10.8.3 on i5 1.7 Ghz) this generates:
#
#       0.350000   0.400000   0.750000 (  0.835234)
#
#   This report shows the user CPU time, system CPU time, the sum of
#   the user and system CPU times, and the elapsed real time. The unit
#   of time is seconds.
#
# * Do some experiments sequentially using the #bm method:
#
#       require 'benchmark'
#
#       n = 5000000
#       Benchmark.bm do |x|
#         x.report { for i in 1..n; a = "1"; end }
#         x.report { n.times do   ; a = "1"; end }
#         x.report { 1.upto(n) do ; a = "1"; end }
#       end
#
#   The result:
#
#              user     system      total        real
#          1.010000   0.000000   1.010000 (  1.014479)
#          1.000000   0.000000   1.000000 (  0.998261)
#          0.980000   0.000000   0.980000 (  0.981335)
#
# * Continuing the previous example, put a label in each report:
#
#       require 'benchmark'
#
#       n = 5000000
#       Benchmark.bm(7) do |x|
#         x.report("for:")   { for i in 1..n; a = "1"; end }
#         x.report("times:") { n.times do   ; a = "1"; end }
#         x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
#       end
#
# The result:
#
#                     user     system      total        real
#       for:      1.010000   0.000000   1.010000 (  1.015688)
#       times:    1.000000   0.000000   1.000000 (  1.003611)
#       upto:     1.030000   0.000000   1.030000 (  1.028098)
#
# * The times for some benchmarks depend on the order in which items
#   are run.  These differences are due to the cost of memory
#   allocation and garbage collection. To avoid these discrepancies,
#   the #bmbm method is provided.  For example, to compare ways to
#   sort an array of floats:
#
#       require 'benchmark'
#
#       array = (1..1000000).map { rand }
#
#       Benchmark.bmbm do |x|
#         x.report("sort!") { array.dup.sort! }
#         x.report("sort")  { array.dup.sort  }
#       end
#
#   The result:
#
#        Rehearsal -----------------------------------------
#        sort!   1.490000   0.010000   1.500000 (  1.490520)
#        sort    1.460000   0.000000   1.460000 (  1.463025)
#        -------------------------------- total: 2.960000sec
#
#                    user     system      total        real
#        sort!   1.460000   0.000000   1.460000 (  1.460465)
#        sort    1.450000   0.010000   1.460000 (  1.448327)
#
# * Report statistics of sequential experiments with unique labels,
#   using the #benchmark method:
#
#       require 'benchmark'
#       include Benchmark         # we need the CAPTION and FORMAT constants
#
#       n = 5000000
#       Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
#         tf = x.report("for:")   { for i in 1..n; a = "1"; end }
#         tt = x.report("times:") { n.times do   ; a = "1"; end }
#         tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
#         [tf+tt+tu, (tf+tt+tu)/3]
#       end
#
#   The result:
#
#                     user     system      total        real
#        for:      0.950000   0.000000   0.950000 (  0.952039)
#        times:    0.980000   0.000000   0.980000 (  0.984938)
#        upto:     0.950000   0.000000   0.950000 (  0.946787)
#        >total:   2.880000   0.000000   2.880000 (  2.883764)
#        >avg:     0.960000   0.000000   0.960000 (  0.961255)

10.9   Installing gems

The documentation available online is at http://rubygems.org/, and it includes a "RubyGems basics" guide.

Here are a few common things that you will often want to do:

  • Show the help and list command gem commands:

    $ gem help
    $ gem h
    
  • List all gem commands:

    $ gem help commands
    
  • Show help for a specific command. Example:

    $ gem help install
    
  • Show list of locally installed gems:

    $ gem list
    $ gem list --local
    
  • Search for (remotely) available gems. Example:

    $ gem search xml
    
  • Show details for one or more remotely available gems. Example:

    $ gem search --details ^libxml-ruby$
    
  • Install a gem that is available remotely. Example:

    $ gem install libxml-ruby
    

Notes:

  • Some commands can be abbreviated to a shorter but unique name.

10.10   Get the current date and time

Use the Time class. Example:

>> current_time = Time.new
    ==>2014-02-14 09:01:29 -0800
>> string_time = current_time.strftime "%Y/%m/%d %H:%M:%S"
    ==>"2014/02/14 09:01:29"
>> puts "Current date and time: #{string_time}"
Current date and time: 2014/02/14 09:01:29
    ==>nil

For more capabilities in dealing with dates and time, see:

And, if you have Ruby information installed, do this at the command line:

$ ri Time
$ ri Time.strftime

11   More Ruby Features and Exercises

[To be added]