Ruby and Database References

Brief Tutorial

The above web resources are a good place to learn about Ruby, but here's my quick summary of the salient points to get you going.

Ruby is a dynamic scripting language, which means that you can run scripts of simple procedural commands as code, just like a shell script. It also means that it is dynamically typed, which means that there is no need, in general, to declare variables. Ruby figures out types and coercions at run-time. Ruby's syntax is very simple and uncluttered. Variables are lower-cased words. The end of line indicates the end of a statement, so line endings aren't needed. Blocks of code are usually marked off simply by an "end". So, at its simplest, Ruby can simply execute a series of commands.

file1.rb
# this is a comment
h = 'Hello'    # strings are single
w = "World"    # or double-quoted.
puts "#{h} #{w}"  # double-quoted allows for interpolation
a, b = 10, 15   # multiple vars and values can be assigned in a single statement
if (b < a * 3)  
  printf "%.2f\n", 22.0/7.0   # parens for functions are optional, if unambiguous
end             # 'end' closes the conditional block
> ruby file1.rb
Hello World
3.14

But Ruby is a full-blown object oriented language. Truly everything is an object. You can define subroutines and classes. Importantly, instead of explicitly specifying a return value, the value of the last executed statement is returned. In addition, like other languages, Ruby comes with a rich set of builtin objects and a standard library full of useful additional classes. Here's what I mean:

file2.rb
require 'time'             # defines class Time.  Part of standard library
def tomorrow(t=Time.new)   # t has default value.  Time.new() is current time as a Time object.
   t += (60*60*24)         # add a day's worth of seconds.  t is return val
end
tom = tomorrow             # parens are optional.  It's common to omit if no args.
after_next = tomorrow(tom)    # 2 days from now

str = '2 days from now is ' + after_next.to_s    # all objs have to_s() for string conversion 

# Here, the numeric expression becomes a Fixnum type, which has a to_s method to convert to a string.
# << means "append".
str << ', which is ' + (60*60*24*2).to_s + ' seconds from now.'

# Here's an example of sub-classing.  I'm extending Time 
# to allow the user to specify standard or military time.  If
# standard time, then I'll report AM and PM.
class MyTime < Time
   def initialize(timemode=:standard)
      @timemode = timemode         # @variables are instance variables, accessible in other methods
      super()                      # call the superclass' initialize (with no args)
   end
   def hhmm
      case @timemode
      when :military               # :string are 'symbols'.  Used when you want a readable label 
         "#{hour}:#{min}"          # this is the same as: self.hour().to_s + ':' + self.min().to_s
      when :standard
         if (hour > 12) then 
            ampm = 'PM'
            h = hour - 12
         else 
            ampm = 'AM'
            h = hour==0?12:hour       
         end
         "#{h}:#{min}#{ampm}"
      else
         raise "Invalid timemode"  # Ruby has a clean implementation of exception handling
      end
   end
end
m = MyTime.new(:standard)
puts m.hhmm              # will print something like "10:35PM"

Before we get too far, note that you can run ruby from the command line, as we've done, with ruby filename or ruby < filename, but you can also run ruby interactively using irb. This is very handy when you're first playing with the language.

> irb
irb(main):001:0> resp = if Math.sqrt(9) == 3 then 'whoop!' else 'huh?' end
=> "whoop!"
irb(main):002:0> 

A very important idiomatic feature of Ruby is that most classes provide iterator methods so you can enumerate over the elements of an array, over a range of values, over the lines of a file, over the key/value pairs in a hash, etc. As a result, you rarely need the conventional for loops. The key to this is the use of a code block as an argument. The method will execute the block for each value. The following example shows some examples as well as demonstrates only a few of the many builtin methods on core classes such as Range, Array, and String:

> irb --simple-prompt
>> x=1.0 .. 5.0                          # this is a range object!
=> 1.0..5.0
>> x.class
=> Range
>> x.step(0.5) { |i| puts i }            # step() iterates through range in 0.5 increments
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5
5.0

>> y = ['apples', 'peaches', 'bananas']  # watch closely..
=> ["apples", "peaches", "bananas"]
>> y.sort
=> ["apples", "bananas", "peaches"]
>> y.sort.reverse                        # methods can be chained
=> ["peaches", "bananas", "apples"]
>> y
=> ["apples", "peaches", "bananas"]
>> y.reverse!                            # by convention "!" means a destructive method
=> ["bananas", "peaches", "apples"]
>> y                                     # see!  y was permanently changed
=> ["bananas", "peaches", "apples"]
>> y.each { |f| print f.upcase + ' ' }  # iterate over y, calling block, setting f to array item
BANANAS PEACHES APPLES

>> y[0]
=> "bananas"
>> y[0].index('nana')                    # find position of 'nana' substring
=> 2
>> y[0].split(/a/)                       # split string at occurrences of 'a'
=> ["b", "n", "n", "s"]
>> y[0].split(/a/).map { |l| l.capitalize }  # iterate over letters
=> ["B", "N", "N", "S"]

It is often common practice for blocks to be an argument to methods that might fail. The block only is executed if the method can run. For example, below, File.open calls the containing block with a File object assigned to f only if the named file can be opened. Then the each method is called, which reads in a line at a time, executing the block for each line.

File.open("/etc/passwd") do |f|
  f.each do |line|
    fields = line.split(/:/)
    puts fields[0]                       # report username, i.e. first field in each line
  end
end 

Tip: To learn about the available classes, see the online Ruby Docs or use the slick 'ri' command-line tool to retrieve documentation.

> ri Array
----------------------------------------------------------- Class: Array
     Arrays are ordered, integer-indexed collections of any object. 
     Array indexing starts at 0, as in C or Java. A negative index is 
     assumed to be relative to the end of the array---that is, an index 
     of -1 indicates the last element of the array, -2 is the next to 
     last element in the array, and so on.

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

Includes:
     Enumerable(all?, any?, collect, detect, each_with_index, entries, 
     find, find_all, grep, include?, inject, map, max, member?, min, 
     partition, reject, select, sort, sort_by, to_a, to_set, zip)

Class methods:
     [], new

Instance methods:
     &, *, +, -, <<, <=>, ==, [], []=, abbrev, assoc, at, clear, 
     collect, collect!, compact, compact!, concat, delete, delete_at, 
     delete_if, each, each_index, empty?, eql?, fetch, fill, first, 
     flatten, flatten!, frozen?, hash, include?, index, indexes, 
     indices, initialize_copy, insert, inspect, join, last, length, 
     map, map!, nitems, pack, pop, push, rassoc, reject, reject!, 
     replace, reverse, reverse!, reverse_each, rindex, select, shift, 
     size, slice, slice!, sort, sort!, to_a, to_ary, to_s, transpose, 
     uniq, uniq!, unshift, values_at, zip, |

> ri Array.push
------------------------------------------------------------- Array#push
     array.push(obj, ... )   -> array
------------------------------------------------------------------------
     Append---Pushes the given object(s) on to the end of this array. 
     This expression returns the array itself, so several appends may 
     be chained together.

        a = [ "a", "b", "c" ]
        a.push("d", "e", "f")
                #=> ["a", "b", "c", "d", "e", "f"]

Next: Accessing a database from Ruby