paulgorman.org/technical

Ruby

(March 2016)

Ruby is an interpreted language.

Statements end at the end of the line (like Python). The line continuation character is a backslash.

Everything is an object.

Ruby files are named like “my_file.rb”.

Utilities

The Ruby REPL is ibr.

The command line documentation utility is ri:

% ri Float
% ri String.chop

The package manager is gem.

Comments

# This is a single-line comment.
foo = "bar" # A comment after a statement
=begin
************************************************
This is a much longer multi-line comment that is
delineated by the '=begin' and '=end' directives (?)
before and after this comment. (The splats are purely
decorative.)
************************************************
=end

Literals

Simple math:

1 + 3 # => 4
2 * 2 # => 4
3 ** 2  # => 9 (exponential)
5 % 2 # => 1 (remainer)

Large numbers can be writting with underscore speparator (just for visual comfort):

1_999_999 == 1999999

String concatenation looks like:

"foo" + "bar" # => "foobar"

Interpolating a variable in a string looks like:

"Hello, #{name}. How are you?"

You can do stuff with strings:

"hello".capitalize # => "Hello"
"hello".reverse # => "olleh"
"hello".next # => "hellp"
"hello".upcase # => "HELLO"
"HELLO".downcase # => "hello"
"HellO".swapcase # => "hELLo"
"hello".length # => 5
"hello".empty? # => false
"hello".include? "hell" # => true
"hello".chop # => "hell"
"hello".reverse.reverse # => "hello"

Type coersion (converting between classes) can be accomplished like this:

3.to_s # => "3" (integer to string)
"3".to_i # => 3 (string to integer)
3.14.to_i # => 3 (float to integer)
3.to_f # => 3.0 (integer to float)

Variables

Use snake_case for variable and method names (and file/directory names):

my_foo = "Bar"
new_my_foo += "Baz"    # => "BarBaz"

All caps constants:

PI = 3.14159265

Global variables begin with a dollar sign:

$global_variable = "foo"

Symbols start with a colon.

:a_symbol

Using symbols in an array of hashes:

widgets = [
    {:color => 'red', :weight => 3},
    {:color => 'blue', :weight => 22},
    {:color => 'green', :weight => 11},
]

widgets.sort_by { |w| w[:color]}.each do |w|
    puts "The weight is #{w[:weight]} pounds."
end

Arrays:

foo = ["foo", "bar", "baz", "bat"]
foo[0] # => "foo"

Arrays can contain mixed classes of objects:

bar = ["fred", 3, foo]

Some array methods:

foo.push('baz')
foo.length # => 4
foo.sort
foo.reverse
foo.delete(3) # Deletes all array element == 3.
foo + ["nerf"] # => ["foo", "bar", "baz", "bat", "nerf"]
foo - ["bar"] # => ["foo", "baz", "bat", "nerf"]

Populate an array:

my_array = []
for i in 0..99 do
    my_array[i] = 1
end

Hashes:

zoo = {
    "tiger" => "Mamal",
    "turtle" => "Reptile",
    "beetle" => "Insect"
}

Add one hash element:

zoo["whale"] = "Mamal"

Iterate through hases like:

zoo.each do |key, value|
    puts key + " : " + value
end

Hash methods:

zoo.each_pair # Same as zoo.each
zoo.each_key
zoo.each_value

Use CamelCase for classes and modules:

class MyClass
    ...
end

Loops

Do loop:

# Prints "foo" three times
i = 3
i.times do # i can be a literal, like '3.times do'
    puts "foo"
end

While loop:

i = 0
while i < 10
    puts "Count: " + i.to_s
    i += 1    # Ruby doesn't have a ++ increment or -- decrement operator.
end

Iterators:

colors = ["red", "blue", "green", "indigo"]
colors.each do |color|
    puts "The color is: " + color
end

also:

colors.length.times do |i|
    puts "Color: " colors[i]
end

also:

colors.sort.each do |color|
    puts "Color: " color
end

Iterate through hases like:

zoo.each do |key, value|
    puts key + " : " + value
end

Conditionals

if foo == "bar"
    x = 1
elsif foo == "baz"
    x = 2
else
    x = 0
end

Functions

Function with no arguments:

def doSomething
    puts "Something"
end

doSomething # => "Something"

Function with arguments:

def doSomethingSpecific(thing, my_default = true)
    if my_default
        puts thing
    end
end

doSomethingSpecific("Pizza") # => "Pizza"

Yield:

def marines
    yield("missle turret")
    yield("bunker")
end

marines do |m|
    puts "Unit: " + m
end

Also:

def players
    yield("Jay", "LaBelle")
    yield("Jake", "Kallie")
end

players do |first, last|
    puts first + " " + last
end

Classes

Variables scopes vis-a-vis classes and objects:

Define classes like:

class Animal
    attr_reader :species # Make @species readable from outside object
    attr_writer :species # Make @species writable outside object
    # attr_accessor makes the object value both readable and writable
    def initialize(species)
        @species = species # The @ denotes a object variable.
    end
end

Instantiate an object from a class:

myAnimal = Animal.new("tiger")

Play with the object:

myAnimal.species # => "tiger"
myAnimal.species = "rabbit"

Another class:

class Vehicle
    attr_accessor :wheels, :topSpeed, :passengerCapacity
    def initialize
        @wheels = @topSpeed = @passengerCapacity = ""
    end
    define to_s
        "Vehicle with " + @wheels + " wheels " \
        + "and a top speed of " + @topSpeed \
        + " and a " + @passengerCapacity + " passenger capacity.\n"
    end
end

Custom object iterators:

class Garage
    def each
        @vehicles.each {|v| yield v}
    end
    def eachTopSpeed
        @vehicles.each {|v| yield v.toSpeed}
    end
end

Public and private object methods:

class SomeClass
    def method1 # default to public
        ...
    end
  private   # subsequent methods are private.  
    def method2 # private method
        ...
    end
    def method3 # private method
        ...
    end
  public    # Set back to public.
    def method4 # public method
        ...
    end
end

Inheritance:

class Motorcycle < Vehicle
    def initialize(wheels, topSpeed, passengerCapacity, sidecar)
        super(wheels, topSpeed, passengerCapacity)
        @sidecar = sidecar
    end
    def to_s
        super + "Sidecar: " + @sidecar
    end
end

Exceptions

begin
    statements which may raise exceptions.
rescue [exception class names]
    statements when an exception occurred.
rescue [exception class names]
    statements when an exception occurred.
ensure
    statements that will always run
end

The last exception is stored in the $! variable (and its type can be determined using $!.type).

Modules

You can just use require to include one Ruby file in another. If the other file is a module, though, it provide a protected namespace.

module Foo
    BAR = 3
    def Foo.narf
        # stuff...
    end
end

Module used like:

require "Foo"
x = Foo::narf

Note that modules also provide mixin/superclass/multiple-inheritance functionality.

Tools

irb is the REPL. pry is an alternative, though not installed by default.

gem is the package manager.

% gem search ^imap
% gem search ^easy_imap$ --details
% gem install easy_imap
% gem list
% gem uninstall easy_imap

ri is the documentation viewer.

% ri rand

rake is a gem that’s like make for Ruby.

Ruby doesn’t seem to have a straight equivalent of Python’s virtualenv, but we can get a similar effect by setting some environment variable in activate.sh and deactivate.sh scripts for a project.

activate.sh:

export OLD_GEMHOME="$GEM_HOME"
export GEM_HOME="$PWD/gems"
export OLD_GEM_PATH="$GEM_PATH"
export GEM_PATH=""
export OLD_PATH="$PATH"
export PATH="$GEM_HOME/bin:$PATH"

deactivate.sh:

export GEM_HOME="$OLD_GEM_HOME"
unset OLD_GEM_HOME

export GEM_PATH="$OLD_GEM_PATH"
unset OLD_GEM_PATH

export PATH="$OLD_PATH"
unset OLD_PATH

Miscellaneous

Random:

1 + rand(6)    # Random number between 1 and 6

Serialize and de-serialize objects:

require 'yaml'

x = [1, 2, 3, 4]

# Serialize to yaml object:
serialized = x.to_yaml

# De-searialize from yaml object:
de-serialized = YAML.load(serialized)

# Serialize to a file:
File.open('/path/to/file.yaml', 'w') { |f| f.write(YAML.dump(x)) }

# De-serialize from a file:
y = YAML.load(File.read('/path/to/file.yaml'))

Sqlite:

require 'sqlite3'

db = SQLite3::Database.new 'mydata.db'     # ... or open() for an existing database.
db.execute 'CREATE TABLE foo (a, b, c)'
db.execute "INSERT INTO foo VALUES ('gold', 'coin', 7)"
x = [
    ['black', 'cube', 5],
    ['yellow', 'tire', 8],
    ['red', 'castle', 4]
]
query = db.prepare "INSERT INTO foo (a, b, c) VALUES (?, ?, ?)"
x.each do |a|
    query.execute(a[0], a[1], a[2])
end