My personally compiled cheat sheet for the Ruby programming language.

##
# Interactive Ruby (irb)
##

# From the console/cli, enter:
irb

##
# Special values
##
nil = no value
# Check for nil with nil? method, such as: if name.nil?

# Comments Begin with #

##
# Constants Start with a capital letter, like: Constant
##

##
# Symbols
##

# Lightweight objects, beginning with a colon.  Best used for comparisons and internal logic.

# Use in place of strings for performance reasons for anything that's not
# displayed to the user.

##
# Arrays and hashes
##
a = [1, 2, 3]
empty = []
empty = Array.new
empty << "new item"

food = {
    'fruit' => 'orange',
    'vegetable' => 'broccoli'
}

histogram = Hash.new(0)

##
# Method declaration
##
def hi
    puts "Hello world"
end

# Call with hi() or just hi.  Parentheses are not necessary if method has no arguments.

# Method name may end with ? ! or =

#  = indicates a setter.
#  ? returns a boolean that answers the question.
#  ! indicates that you should be cautious calling
#   that version of the method because it may be
#   destructive (such as sort! which is in-place,
#   rather than regular sort, which copies)

##
# Method with argument
##
def hi(name)
    puts "Hello #{name}"
end

hi("Steve")

##
# Class declaration
##

class Greeter
    def initialize(name = "World")
        @name = name
    end

    def say_hi
        puts "Hi #{@name}"
    end

    def say_bye
        puts "Bye #{@name}"
    end

    def return_zero
        return 0
    end
end

##
# In the above:
##
@name is an instance variable
@@foo is a class variable

# Instantiation and use
g = Greeter.new("Steve")
g.say_hi
g.say_bye

##
# Class and object introspection
##

Greeter.instance_methods # returns an array of every method offered by the class, including inherited methods.
Greeter.instance_methods(false) # returns just the methods defined in the subclass.

g.respond_to?("say_hi") # determines if the object responds to the named method.
myObject.is_a?(Integer) # to determine class of object

##
# Instance variable accessors
##
class Greeter
    attr_accessor :name
end

# Allows you to get the value of name with the name method and set it
# with the name= method.

# attr_reader for read-only, attr_writer for write-only

##
# Subclassing
##

class Student < Human
  # stuff here...
end

# Conditional structure

if expression
    stuff
elsif
    other stuff
else
    other stuff
end

##
# While loop
##
while weight < 100 and numPallets <= 30
    weight += 1
end

##
# List iteration
# each is a method that accepts a code block
# (the bit between do and end)
##
names.each do |name|
    puts "Hello #{name}"
end

##
# Code blocks
##
{ puts "Hello" }

do
    puts "Hello"
end

# yield statement executes a code block:
def callBlock
    yield
    yield
end

callBlock { puts "Hello" }

# Results in:
# Hello
# Hello

##
# Joining string list elements
##
names.join(", ")

##
# Regular expression matching
##
if line =~ /Perl|Python/
    puts "Scripting language mentioned: #{line}"
end

##
# Regular expression replacing
##
line.sub(/Perl/, 'Ruby') # replace first
line.gsub(/Python/, 'Ruby') # replace every

##
# Ranges
##
(1..3) is the numbers 1 through 3
(0...5) is the numbers 0 through 4

##
# Heredocs
##
puts <<EOF
    Blah
    Blah
    Blah
EOF

##
# String interpolation or "embedding"
# the result of some code inside a string.
##
name = "Andrew"
greeting = "Hello, my name is #{name}"
addition = "25 x 8 = #{25 * 8}"

##
# There are two ways to create an empty array:
##
my_arr = Array.new
my_other_arr = []

 # init an array with values
my_third_array = ["one", "two", "three"]

##
# Hashes
##
my_hash = Hash.new
my_other_hash = {}

my_hash["name"] = "Andrew"
my_hash[:age] = 20

# a hash with objects
person = {
  :name => "Joe",
  :age => 35,
  :job => "plumber"
}

# Sort a Ruby Hash by number value

metrics = { "sitea.com" => 745, "siteb.com" => 9, "sitec.com" => 10 }
metrics.sort_by { |key, value| value }
# ==> [["siteb.com", 9], ["sitec.com", 10], ["sitea.com", 745]]

##
# Methods on Objects
##

# a ruby method

def sayMoo
  puts 'mooooooo...'
end

# sayMoo
# sayMoo
# puts 'coin-coin'
# sayMoo
# sayMoo

# Method Parameters

def sayMoo numberOfMoos
  puts 'mooooooo...'*numberOfMoos
end

# sayMoo 3
# puts 'oink-oink'
# sayMoo  #  This should give an error because the parameter is missing.
# mooooooo...mooooooo...mooooooo...
# oink-oink
# <ArgumentError: wrong number of arguments (0 for 1)>

# Local Variables

def doubleThis num
  numTimes2 = num*2
  puts num.to_s+' doubled is '+numTimes2.to_s
end

# doubleThis 44
# 44 doubled is 88

def doubleThis num
  numTimes2 = num*2
  puts num.to_s+' doubled is '+numTimes2.to_s
end

# doubleThis 44
# puts numTimes2.to_s
# 44 doubled is 88
# <NameError: undefined local variable or method `numTimes2' for #<StringIO:0x82ba21c>>

##
# Return Values
##

def englishNumber number
  if number < 0  #  No negative numbers.
    return 'Please enter a number that isn't negative.'
  end
  if number == 0
    return 'zero'
  end

  #  No more special cases!  No more returns!

  numString = ''  #  This is the string we will return.

  onesPlace = ['one',     'two',       'three',    'four',     'five',
               'six',     'seven',     'eight',    'nine']
  tensPlace = ['ten',     'twenty',    'thirty',   'forty',    'fifty',
               'sixty',   'seventy',   'eighty',   'ninety']
  teenagers = ['eleven',  'twelve',    'thirteen', 'fourteen', 'fifteen',
               'sixteen', 'seventeen', 'eighteen', 'nineteen']

  #  "left" is how much of the number we still have left to write out.
  #  "write" is the part we are writing out right now.
  #  write and left... get it?  :)
  left  = number
  write = left/100          #  How many hundreds left to write out?
  left  = left - write*100  #  Subtract off those hundreds.

  if write > 0
    #  Now here's a really sly trick:
    hundreds  = englishNumber write
    numString = numString + hundreds + ' hundred'
    #  That's called "recursion".  So what did I just do?
    #  I told this method to call itself, but with "write" instead of
    #  "number".  Remember that "write" is (at the moment) the number of
    #  hundreds we have to write out.  After we add "hundreds" to "numString",
    #  we add the string ' hundred' after it.  So, for example, if
    #  we originally called englishNumber with 1999 (so "number" = 1999),
    #  then at this point "write" would be 19, and "left" would be 99.
    #  The laziest thing to do at this point is to have englishNumber
    #  write out the 'nineteen' for us, then we write out ' hundred',
    #  and then the rest of englishNumber writes out 'ninety-nine'.

    if left > 0
      #  So we don't write 'two hundredfifty-one'...
      numString = numString + ' '
    end
  end

  write = left/10          #  How many tens left to write out?
  left  = left - write*10  #  Subtract off those tens.

  if write > 0
    if ((write == 1) and (left > 0))
      #  Since we can't write "tenty-two" instead of "twelve",
      #  we have to make a special exception for these.
      numString = numString + teenagers[left-1]
      #  The "-1" is because teenagers[3] is 'fourteen', not 'thirteen'.

      #  Since we took care of the digit in the ones place already,
      #  we have nothing left to write.
      left = 0
    else
      numString = numString + tensPlace[write-1]
      #  The "-1" is because tensPlace[3] is 'forty', not 'thirty'.
    end

    if left > 0
      #  So we don't write 'sixtyfour'...
      numString = numString + '-'
    end
  end

  write = left  #  How many ones left to write out?
  left  = 0     #  Subtract off those ones.

  if write > 0
    numString = numString + onesPlace[write-1]
    #  The "-1" is because onesPlace[3] is 'four', not 'three'.
  end

  #  Now we just return "numString"...
  numString
end

puts englishNumber(  0)
puts englishNumber(  9)
puts englishNumber( 10)
puts englishNumber( 11)
puts englishNumber( 17)
puts englishNumber( 32)
puts englishNumber( 88)
puts englishNumber( 99)
puts englishNumber(100)
puts englishNumber(101)
puts englishNumber(234)
puts englishNumber(3211)
puts englishNumber(999999)
puts englishNumber(1000000000000)

# zero
# nine
# ten
# eleven
# seventeen
# thirty-two
# eighty-eight
# ninety-nine
# one hundred
# one hundred one
# two hundred thirty-four
# thirty-two hundred eleven
# ninety-nine hundred ninety-nine hundred ninety-nine
# one hundred hundred hundred hundred hundred hundred

##
# Arrays and Iterators
##

names = ['Ada', 'Belle', 'Chris']

puts names
puts names[0]
puts names[1]
puts names[2]
puts names[3]  #  This is out of range.

# Ada
# Belle
# Chris
# Ada
# Belle
# Chris
# nil

languages = ['English', 'German', 'Ruby']

languages.each do |lang|
  puts 'I love ' + lang + '!'
  puts 'Don't you?'
end

puts 'And let's hear it for C++!'
puts '...'

# I love English!
# Don't you?
# I love German!
# Don't you?
# I love Ruby!
# Don't you?
# And let's hear it for C++!
# ...

3.times do
  puts 'Hip-Hip-Hooray!'
end

# Hip-Hip-Hooray!
# Hip-Hip-Hooray!
# Hip-Hip-Hooray!

foods = ['artichoke', 'brioche', 'caramel']

puts foods
puts
puts foods.to_s
puts
puts foods.join(', ')
puts
puts foods.join('  :)  ') + '  8)'

200.times do
  puts []
end

# artichoke
# brioche
# caramel
#
# artichokebriochecaramel
#
# artichoke, brioche, caramel
#
# artichoke  :)  brioche  :)  caramel  8)

favorites = []
favorites.push 'raindrops on roses'
favorites.push 'whiskey on kittens'

puts favorites[0]
puts favorites.last
puts favorites.length

puts favorites.pop
puts favorites
puts favorites.length

# raindrops on roses
# whiskey on kittens
# 2
# whiskey on kittens
# raindrops on roses
# 1

##
# Array Iterator example
##

sites = ["vpn", "ftp", "m"]

sites = sites.map do |s|
    "#{s}.jonlabelle.com"
end

# => ["net.jonlabelle.com", "psd.jonlabelle.com", "mobile.jonlabelle.com"]

# The map method collects whatever values are returned from each iteration of
# the block. Then, an array of those values is returned from the method. In this
# case, we're reassigning the sites variable to the new array.

# There's a better way to do this, though. Several Ruby methods have
# duplicates with the exclamation mark (or bang); this means they are
# destructive: they replace the value they are working on. So the above
# could be done this way:

sites.map! { |site_prefix| "#{site_prefix}.jonlabelle.com" }

##
# The method "<<" is commonly used with arrays. It appends a value to its receiver.
##

ages = []
for person in @people
  ages << person.age
end

##
# Ruby has a shortcut for creating an array of words:
##

a = [ 'ant', 'bee', 'cat', 'dog', 'elk' ]
# this is the same:
a = %w{ ant bee cat dog elk }

toast = Proc.new do
  puts 'Cheers!'
end

toast.call
toast.call
toast.call

# Cheers!
# Cheers!
# Cheers!

doYouLike = Proc.new do |aGoodThing|
  puts 'I *really* like '+aGoodThing+'!'
end

doYouLike.call 'chocolate'
doYouLike.call 'ruby'

##
# Methods Which Take Procs
##

def doSelfImportantly someProc
  puts 'Everybody just HOLD ON!  I have something to do...'
  someProc.call
  puts 'Ok everyone, I'm done.  Go on with what you were doing.'
end

sayHello = Proc.new do
  puts 'hello'
end

sayGoodbye = Proc.new do
  puts 'goodbye'
end

doSelfImportantly sayHello
doSelfImportantly sayGoodbye

# Everybody just HOLD ON!  I have something to do...
# hello
# Ok everyone, I'm done.  Go on with what you were doing.
# Everybody just HOLD ON!  I have something to do...
# goodbye
# Ok everyone, I'm done.  Go on with what you were doing.

def doUntilFalse firstInput, someProc
  input  = firstInput
  output = firstInput

  while output
    input  = output
    output = someProc.call input
  end

  input
end

buildArrayOfSquares = Proc.new do |array|
  lastNumber = array.last
  if lastNumber <= 0
    false
  else
    array.pop                         #  Take off the last number...
    array.push lastNumber*lastNumber  #  ...and replace it with its square...
    array.push lastNumber-1           #  ...followed by the next smaller number.
  end
end

alwaysFalse = Proc.new do |justIgnoreMe|
  false
end

puts doUntilFalse([5], buildArrayOfSquares).inspect
puts doUntilFalse('I'm writing this at 3:00 am; someone knock me out!', alwaysFalse)

# [25, 16, 9, 4, 1, 0]
# I'm writing this at 3:00 am; someone knock me out!

##
# Methods Which Return Procs
##

def compose proc1, proc2
  Proc.new do |x|
    proc2.call(proc1.call(x))
  end
end

squareIt = Proc.new do |x|
  x * x
end

doubleIt = Proc.new do |x|
  x + x
end

doubleThenSquare = compose doubleIt, squareIt
squareThenDouble = compose squareIt, doubleIt

puts doubleThenSquare.call(5)
puts squareThenDouble.call(5)

# 100
# 50

##
# Passing Blocks (Not Procs) into Methods
##

class Array

  def eachEven(&wasABlock_nowAProc)
    isEven = true  #  We start with "true" because arrays start with 0, which is even.

    self.each do |object|
      if isEven
        wasABlock_nowAProc.call object
      end

      isEven = (not isEven)  #  Toggle from even to odd, or odd to even.
    end
  end

end

['apple', 'bad apple', 'cherry', 'durian'].eachEven do |fruit|
  puts 'Yum!  I just love '+fruit+' pies, don't you?'
end

#  Remember, we are getting the even-numbered elements
#  of the array, all of which happen to be odd numbers,
#  just because I like to cause problems like that.
[1, 2, 3, 4, 5].eachEven do |oddBall|
  puts oddBall.to_s+' is NOT an even number!'
end

# Yum!  I just love apple pies, don't you?
# Yum!  I just love cherry pies, don't you?
# 1 is NOT an even number!
# 3 is NOT an even number!
# 5 is NOT an even number!

# So to pass in a block to eachEven, all we had to do was stick the block
# after the method. You can pass a block into any method this way, though
# many methods will just ignore the block. In order to make your method not
# ignore the block, but grab it and turn it into a proc, put the name of the
# proc at the end of your method's parameter list, preceded by
# an ampersand (&). So that part is a little tricky, but not too bad, and
# you only have to do that once (when you define the method). Then you
# can use the method over and over again, just like the built-in methods
# which take blocks, like each and times. (Remember 5.times do...?)

def profile descriptionOfBlock, &block
  startTime = Time.now

  block.call

  duration = Time.now - startTime

  puts descriptionOfBlock+':  '+duration.to_s+' seconds'
end

profile '25000 doublings' do
  number = 1

  25000.times do
    number = number + number
  end

  puts number.to_s.length.to_s+' digits'  #  That is, the number of digits in this HUGE number.
end

profile 'count to a million' do
  number = 0

  1000000.times do
    number = number + 1
  end
end

# 7526 digits
# 25000 doublings:  0.246768 seconds
# count to a million:  0.90245 seconds

# Article: Ruby for Newbies: Working with Classes
# http://net.tutsplus.com/tutorials/ruby/ruby-for-newbies-working-with-classes/

##
# Modifying a Blank Object
##

o = Object.new
def o.my_method
  1 + 1
end
print o.my_method

# Properties

def o.set_name ( name )
  @name = name
end

def o.get_name
  @name
end

o.set_name "Andrew"
print o.get_name # => Andrew

##
# Creating a Class
##

class Person

  # the GET method for name
  def name
    @name
  end

  # the SET method anem
  def name= name
    @name = name
  end

end

p = Person.new
p.name = "jon"
print p.name

class Person2

  # Constructor
  def initialize (name, age, job = 'unemployed')
    @name = name
    @age  = age
    @job  = job

    @@count += 1
  end

  # Pass attr_accessor the names of your properties as symbols. This creates
  # the var and var= methods for you. Then, you'll be able to use the
  # @ versions wherever you need to in your code.
  attr_accessor :name, :age, :job

  # If you want read-only or write-only properties, you can use attr_reader
  # or attr_writer, respectively. Of course, if you use, say, attr_writer,
  # you can then create a custom reader method if needed.

  # Creating Class Methods and Variables
  @@count = 0

  # EXPOSE THE COUNT CLASS VARIABLE
  # Note – Ruby doesn't really have class methods (or static methods, as some
  # languages call them). There's actually a pretty cool bit of "magic" going
  # on under the surface that makes these look like class methods. We'll get
  # into that—usually called metaprogramming—in a future chapter.
  def self.count
    @@count
  end

  protected

  def m2 # this method is protected
    puts 'Hello'
  end

  # Private methods are functions that can only be called by other functions
  # within the class; they aren't exposed to the outside world. The usual way
  # to create private methods is this: under all your public code (the
  # instance and class methods), add the keyword private. Any functions that
  # follow this keyword are private.
  private

  def get_real_weight
    @weight
  end

end

# p2 = Person2.new("jon labelle", 33, "programmer")
# print p2.job
# print p2.get_real_weight

joe  = Person2.new("Joe", 35, "plumber")
jill = Person2.new("Jill", 13)
bob  = Person2.new "Bob", 70

print Person2.count # => 3

##
# Working with Dates
##
require 'date'

aNewDate=Date::new(2004,8,12) # year, month, day
aNewDate::to_s                # gives u the saved date as a string
aNewDate=aNewDate::+1         # increases by 1 day
aNewDate=aNewDate::-1         # decreases by 1 day
day=aNewDate::wday()          # gives the day of the week as a number, sun=0, mon=1....
week=aNewDate::cweek()        # gives the week of year as a number
toDaysDate=Date::today()      # creates a Date object with todays date

##
# Ruby Ternary Operator
##

x = 10
puts x == 10 ? "x is ten" : "x is not ten"

# Or.. an assignment based on the results of a ternary operation:
LOG.sev_threshold = ENVIRONMENT == :development ? Logger::DEBUG : Logger::INFO

##
# Regular Expressions
##

def replace_underscores_with_space(val)
  return val.gsub(/_{1,2}/, ' ')
end

def remove_first_three_int_and_space(val)
  return val.gsub(/A[0-9]{1,3}s/i, '')
end

def remove_extra_spaces(val)
  return val.gsub(/s{2,}/i, ' ')
end

def search_and_replace(searchSubject, searchFor, replaceWith)
  return searchSubject.sub(searchFor, replaceWith)
end

def fix_dashes(val)
  return val.gsub(/-/i, ' - ')
end

##
# Use ranges instead of complex comparisons for numbers in Ruby
##

# No more if x > 1000 && x < 2000 nonsense. Instead:

year = 1972
puts case year
        when 1970..1979: "Seventies"
        when 1980..1989: "Eighties"
        when 1990..1999: "Nineties"
      end

##
# File system
##

# Open a file and read its contents

file='GettysburgAddress.txt'
f = File.open(file, "r")
f.each_line { |line|
  puts line
}
f.close

# or
file='GettysburgAddress.txt'
File.readlines(file).each do |line|
  puts line
end

# append text to a file.
open('myfile.out', 'a') { |f|
  f.puts "Hello, world."
}

# Read, write file sample
#
# The following code is a very simple example of how to use Marshal. At the
# beginning of the program, if a file called counter exists in the current
# directory, it will open it and load an object from it. This object will be a
# number, which will be incremented and then stored back in the file. This is a
# clone of a common "Hello World" PHP script,
# to implement a counter for a web page.

counter = if File.exists?('counter')
  File.open('counter') do|file|
    Marshal.load(file)
  end
else
  0
end

puts "Counter is currently at #{counter}"
puts "incrementing to #{counter + 1}"

counter += 1

File.open('counter','w') do|file|
  Marshal.dump(counter, file)
end

# Remove repeated lines in a file

def squeeze
  ARGV.each{ |f|
    fl = File.new(f).readlines
    fl.uniq!
    esc = File.open("#{f}","w")
    fl.each{ |l| esc.print l}
    esc.close
  }
end

# `Require` every file from directory

Dir['lib/*.rb'].each{ |f|
  require f
}

# Bulk rename files

basepath = "/mp3/Billboard Hot 100 (08.10.2011)"
Dir.chdir(basepath)

Dir.glob("*.mp3") { |filename|
  file = File.new(filename)

  # remove the first 3 numbers and dash from start of filename
  new_filename = filename.gsub(/Ad{1,3}/, '')

  puts "Renaming #{filename} to #{new_filename}..."
  File.rename(filename, new_filename)
}

# Recursive delete

def recursive_delete(dir)
  files = []
  Dir.foreach(dir) do |fname|
    next if fname == '.' || fname == '..'
    path = dir + '/' + fname
    if File.directory?(path)
      puts "dir #{path}"
      recursive_delete(path)
    else
      puts "file #{path}"
      files << path
    end
  end
  files.each do |path|
    puts "delete file #{path}"
    #File.delete(path)
  end
  puts "delete dir #{dir}"
  Dir.rmdir(dir)
end

# Parse an XML element

def parseSingleElement(filename, elementName)
  (REXML::Document.new(File.new(filename))).elements[elementName].text
end

# `which` command Ruby equivalent

def which(name)
   ENV['PATH'].split(':').map { |p| File.join p, name }.find { |p| File.file? p and File.executable? p }
end

# Simple encryption class
#
# This class allows you to encrypt strings using Rinjdael encryption routine
# provided in the ruby crypt library (http://crypt.rubyforge.org/installation.html).
# The encrypted string can also be passed in the url.

require 'crypt/Rijndael'

class Crypto

  def initialize
    key="12345678"
    @@r = Crypt::Rijndael.new(key)
  end

  def encrypt(str)
    str=str.to_s

    if str.length > 16
      raise "string length greater that 16 bytes is not supported"
    end

    (16-str.length).times {str += '~'}
    a = @@r.encrypt_block(str)
    [a].pack('m').tr('+','@')

  end

  def decrypt(str)
    str=str.tr('@','+')
    str = str.unpack('m')
    a = @@r.decrypt_block(str[0])
    a = a.tr('~','')
  end
end

##
# Ruby file and system utils
##

def sh command
  system command
  unless $?.success?
    log "failed: #{command}"
    exit 1
  end
end

def write_host_entry ip, host
  add_line_to_file "/etc/hosts", "#{ip} #{host}"
end

def add_line_to_file path, line
  original_lines = File.read(path).each_line.map{|l| l.chomp}
  if original_lines.include?(line)
    log "already added line to '#{path}': #{line}"
  else
    log "adding line to '#{path}': #{line}"
    sh "echo '#{line}' >> #{path}"
  end
end

def add_line_to_bash_profile line
  add_line_to_file("/etc/profile", line)
  system "source /etc/profile"
end

def package_installed? package_name
  installed = `dpkg-query -W --showformat='${Status}n' #{package_name}` =~ /install ok installed/
  log "#{package_name} already installed..." if installed
  installed
end

def add_package_source source
  add_line_to_file "/etc/apt/sources.list", "deb http://debian.datastax.com/community stable main"
  @updated_apt_cache = false
end

def ensure_package package_name
  unless package_installed? package_name
    unless @updated_apt_cache
      sh "apt-get update -y --fix-missing"
      @updated_apt_cache = true
    end
    sh "apt-get install -y #{package_name}"
  end
end

def log line
  puts "[vosbox] #{line}"
end

def ensure_file path, content
  path = File.expand_path(path)
  if File.exist?(path) && File.read(path) == content
    log "exists: #{path}"
  else
    log "create: #{path}"
    File.open(path, "w"){|f| f.write(content)}
    sh "chown vagrant:vagrant #{path}"
  end
end

def ensure_symlink from, to
  if File.symlink?(to)
    log "symlink already exists: #{from} => #{to}"
  else
    log "creating symlink: #{from} => #{to}"
    sh "ln -s #{from} #{to}"
  end
end

def ensure_directory directory
  if Dir.exist? directory
    log "directory exists: #{directory}"
  else
    log "creating directory: #{directory}"
    sh "mkdir -p #{directory}"
    sh "chown -R vagrant:vagrant #{directory}"
  end
end

def ensure_file_copy from, to
  sh "cp #{from} #{to}"
end

def ensure_gem gem
  if Gem.available? gem
    log "gem '#{gem}' already installed..."
  else
    sh "gem install #{gem} --no-rdoc --no-ri"
  end
end

# Resources
# - http://pleac.sourceforge.net/pleac_ruby/