Ruby Course Notes
- Contact:
- dkuhlman (at) reifywork (dot) com
- Address:
http://www.reifywork.com
- Revision:
- 1.1.1
- Date:
- December 23, 2024
- 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.
1 Introductions Etc.
1.1 Resources
Look here for help:
1.2 Preliminaries
Text editor -- Use any good programmer text editor. See:
1.2.1 The Ruby interactive shell -- irb
For more info on irb, see https://docs.ruby-lang.org/en/3.0/IRB.html
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:
If the Ruby information system is installed on your system, you can get help from the command line. For example:
$ ri Array.sort = Array.sort (from ruby core) o o o
From the document you are currently reading.
At the Ruby documentation Web site: http://ruby-doc.org/
Documentation on Ruby's standard classes is here: https://ruby-doc.org/3.3.6/index.html
"Programming Ruby The Pragmatic Programmer's Guide": http://www.ruby-doc.org/docs/ProgrammingRuby/
"Ruby programming" at the Wikibooks Web site: http://en.wikibooks.org/wiki/Ruby_Programming
"The Ruby User's Guide": https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/
"Ruby Essentials": http://www.techotopia.com/index.php/Ruby_Essentials
"Documentation for Ruby 3.3": https://docs.ruby-lang.org/en/3.3/index.html
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:
Add the following as the first line of the script:
#!/usr/bin/env ruby
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 -rdebug 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.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 an 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 one has [embedded] brackets] ==>"this one 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/3.3.6/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: https://ruby-doc.org/3.3.6/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 ask 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:
A file object has a number of ".each" methods. You can learn about them with ri. Or look here:
In particular, there is an file_obj.each_with_index that gives an index number with each line.
Don't forget to close the file when you are finished, for example: file_obj.close.
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
Lots more information about regular expressions in Ruby is here:
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
More information about ranges:
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:
Defines several exception classes. The are subclasses class Exception.
Implements a function (f1) that calls another funtion (f2) that raises an exception.
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:
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
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, lambda and Proc, closures, and blocks
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
For more information on the Ruby Struct class see https://ruby-doc.org/3.3.6/Struct.html
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 a 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:
>> proc3 = lambda {|value| return value * 3 } => #<Proc:0x00007913af462ff8 (irb):2 (lambda)> >> proc3[4] => 12
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:
When you want a value (i.e. you want your proc or lambda to return a value), use a lambda.
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
For more information about Proc and lambda, see:
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 the 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:
#!/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 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 understand 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 (https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/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:
The code above defines two modules, giving us two separate namespaces.
Names which are the same but are in these two different modules do not conflict with each other.
But, global variables (prefixed with "$") are global across modules in the same file. Notice how we can refer to $global_var1, defined in ModuleA, from ModuleB.
When we define names in these two modules, we prefix the name with the module name.
When we use names defined in these two modules, we prefix the name with the module name.
To create a name that is local to a module but is global across methods within that module, prefix the name with "@@".
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:
The above code used require_relative so that Ruby would search for our module in the current directory.
Or, we could add the current directory to the $: special variable. Example:
$:.push '.' require 'module_example1'
But, what is likely a better solution is to add a directory to the RUBYLIB environment variable, before we run the example. Then we use require, not require_relative. Example:
$ export RUBYLIB=$RUBYLIB:. $ ./module_example2.rb Hello from ModuleA Hello from ModuleB
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
Below, we discuss and provide information about libxml. However, you may also want to consider the alternative REXML - https://github.com/ruby/rexml.
"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.
#!/usr/bin/env ruby $Usage = <<END synopsis: print out the tree of elements in an XML document. usage: $ ruby test09.rb my_doc.xml END 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:
A tutorial -- http://zetcode.com/db/sqliteruby/
API documentation -- http://sqlite-ruby.rubyforge.org/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.5 File input and output
10.6 Unit tests
10.6.1 A simple example
10.6.2 Unit test suites
10.6.3 Additional unittest features
10.6.4 Guidance on Unit Testing
10.7 Debugging ruby scripts
Install the debug Gem:
$ gem install debug
Then run your test with rdbg, for example:
$ rdbg mytest.rb
At the rdbg prompt, type "help" (or "h") for information about supported debugging commands.
See https://github.com/ruby/debug for more information.
An alternative, though possibly not as good, is to use the built-in debug module to debug your scripts with the interactive, command oriented debugger. Add the command debug binding in your Ruby script where you want to drop into the debugger, the start it up with the following:
$ ruby -rdebug my_scipt.rb
Or, add require 'debug' to your script and, at the location where you want to drop into the debugger, add binding.break.
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 # Or, $ gem install -h
Show list of locally installed gems:
$ gem list $ gem list --local
Use the --details option with the list command to learn where a gem is installed. Example:
$ gem list --details
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
Install a gem in the users local directory (~/.local on my machine) rather than in the system-wide location. Example:
$ gem install --user-install asciidoctor
You can also set gem options in ~/.gemrc. See https://docs.ruby-lang.org/en/3.3/Gem/ConfigFile.html. Here is a sample .gemrc:
install: --user-install update: --user-install
Notes:
Some gem 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]
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: