Ruby for Kittens

Ruby has Class

A Basic Ruby class

# <-- character causes remainder of line to be treated as a comment

class Kitten < BaseClass
  @cvar = "Class Variable" # Declares a class variable with initial setting
  attr_assessor :ivar      # Creates @ivar instance variable, o.name() and o.name=()
  attr_reader :ivar        # Creates @ivar instance variable and o.name reader
  $global_var = 0          # Try to avoid global variables, okay?

  # Called after Kitten.new to initialize the object
  def initialize(*args)
  end

  # A Class method (General Kitten-related function) Called as: Kitten::method()
  def self.class_method(*args)
    @cvar                  # Returs a class variable
  end

  # An Instance method (specific kitten), called as kitten.says()
  def says
    @ivar = 1              # Access instance variables
    "Meow"                 # always returns last value or last statement
  end
end

kitten = Kitten.new()      # Creates instance of Kitten
kitten.says                # => "Meow"
Kitten.new.says            # You can chain the methods together! # => Meow

Flow Control

if animal.says == "Meow"
  puts "Its a kitty!"
elsif animal.says == "Woof"
  puts "Its a puppy!"
else
  puts "I don't know what that is"
end

puts "hi puppy" if animal.says == "Woof"
puts "Not a kitty!" unless animal.says "Meow"

# Case statement, returns matching value, can be assigned to returned
it_is_a = case animal.says
  when "Meow" then "kitty"
  when "Woof" then "doggie"
  else "i don't know"
end

# Loops: you shouldn't need to use them! See enumeration
for i in 1..10 do 
   next if i == 7           # Skips rest of iteration
   break if i == 8         # Ends loop
   redo if try_again       # Restart iteration  
   puts i  
end

while condition do 
  something
end 

begin
   something
while condition

until condition do 
   something
end

for variable in expression_list do  # foreach 
   something
end

Blocks

Ruby’s Blocks are its most important and cool aspect, kitty. Every funtion (method) call can take an optional block to inject some customization into an abstract, generic routine!

Blocks can optionally take arguments, and are in two formats:

a.method { "hello" }       # One-line block (by convention), returns value when called
a.method do                # Multi-line block (by convention), returns value when called
      "hello"                  # The last value references in a block or statement is returned
end
a.method { |arg| arg.upcase } # Blocks can accept arguments like methods
a.method do |arg|
  arg.upcase
end

Of course, blocks are mostly used for Ruby loops and enumerations (more info later….)

array.each { |item| puts item } # Iterates over, and prints each item in an array
records.each do |record|   # Iterates over a set of records
  record.something
end

You can save a block until later by using lambda to store the code. Its like an undercover function!

code = lambda {|*args} something }
code.call(1, 2, 3)

Strings

name = "Maru"
i = 12
name = "I am #{i} cat years old"     # Interpolates value of i
name = %Q(I am #{i} cat years old)   # %Q for ", %q for ' (not interpolated)
"123 boom!".to_i           # => 123  (to integer, stopping at non-numeric)
"asdf"[3]                  # => "f"  Character at position
"asdf"[2,2]                # => "df" Substring
name[2,2] = "RU"           # a is now "MaRU"
"hello".index('lo')        #=> 3
name.empty?                # True if length of zero

"maru".capitalize          # => Maru
"Maru".upcase              # => "MARU"
"Maru".downcase            # => "maru"

"Résumé".size              # => 6 (characters)
"Résumé".bytes.count       # => 8 (bytes in UTF-8)
.
"hello".gsub(/[aeiou]/, '*') # => "h*ll*"  Global substitition by regex
"hello " . "there"         # => "hello there", operation concatenation

Numbers

123.class                  #=> FixNum, so its an integer
12.3.class                 #=> Float, a floater
020                        #=> 16, its octal, baby!
0x32                       #=> 50, hexidecimal dude
12.to_s                    # => "12", now a string

Ranges

1..10                      # => A range of numbers, 1 to 10
(1..10).class              # => Range, Syntax to operate on ranges
(1..10).each {|i| puts i}  # => Prints 1 through 10, a cool loop

Hashes

hash = Hash.new("default value") # Create hash with constructor
hash = {"key" => "value", :key=>123, 1=>"one" } # Without constructor
hash = { name:value }      # New syntax, same as { :name => value }
hash[:key]                 # => 123
hash[:newkey] = 234
hash.keys                 # Returns array of key names
hash.delete("key")
hash.delete_if { |key,value| key.blank? } # Deletes pairs returning true
hash.each { |key, value| puts key, value }
hash.empty?
hash.fetch("key") { |key| puts "#{key} not found" }
hash.has_key?(key)         # alias: include?() key?() member?()
hash = hash.merge(otherhash) 
hash.merge!(otherhash)
hash.size                  # Alias: length()

Arrays

a = Array.new()            # Create with Constructor
a = [1, :hello, "there", object] # Without constructor
a = %w(each space-delimited word is an element) # Also: %w{}, %w//, etc.

a.first
a[0]                       # first element
a.last
a[-1]                      # Last element
a[start,length]            # slice of array
a[2..6]                    # 3rd through 7th elements using Range

a.compact!                 # Removes nil items in array
a.delete(value)            # Deletes all items of the given value
a.delete_at(index)
a.flatten                  # Returns array with sub-arrays flattens into a single one
a.include?(value)          # True if value is in the array
a.join(", ")               # Returns string of values and separator "1, 2, 3"
a.length                   #  Also: size
a.reverse                  # new array with items reversed
a.sort {|a,b| a<=>b }      # Sort with optional compare block
a.uniq                     # New array without duplocated. also: uniq!
a | otherarray             # Union of arrays, removes duplicates

a.push(value)              # Appends to array
v = a.pop                  # Removes and returns last item
v = a.shift                # Removes first item from list
a.unshift(v)               # Prepends value to left of array

a = a.map { |item| item * 2 } # New array with items doubled
a.delete_if {|item| item < 0 } 
a.each { |item| puts item }
a.fetch(index) {|i| "puts index #{i} out of range" }

Enumeration

123.times do
   something
end
123.times { |i| puts i }
arr.each { |item| puts i }
arr = arr.map {|item| item.upcase } # Runs block once per element, returns array of results
(5..10).inject {|sum, n| sum + n } #=> 45, Loops, sends object for op, returns at end
(5..10).r {|sum, n| sum + n } #=> 45, Loops, sends object for op, returns at end
(1..10).reject {|i|  i % 3 == 0 }   #=> [1, 2, 4, 5, 7, 8, 10], those elements evaluated to false
(1..10).reject {|i|  i % 3 == 0 }   #=> [1, 2, 4, 5, 7, 8, 10], those elements evaluted to true
%w(rhea kea flea).sort         #=> ["flea", "kea", "rhea"], sorted according to <=> operator 
(1..10).sort {|a,b| b <=> a}   #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], reverse sort

Calling Methods

When you call a ruby method, there are several rules in effect

  • Each pre-determined argument is listed. Defaults can be specified for those not given;
  • Additional arguments can be gathered with the “splat” operatior “*args”
  • Any hash-like “options” are gathered as the last item of the “additional arguments” array
  • A passed block matches the name with the & operator, such as &block

To Illistrate:

def a(a)                   # Takes one argument
def a(a, b=1)              # Takes one argument, optional argument b defaults to 1
def b(a, b=1, *args)       # Takes one argument, and a variables number of args into arrary "arg"
def c(a,*args, &block)     # ... as above, bit with an optional block 
  options = args.last.is_a?(Hash) ? args.pop : {} # Take hash-option off of end of variable arguments
end                        #

a(1)                       # sets a=1
b(1, 2, 3)                 # sets a=1, b=1, args = [3]
b(1, 2, :opt=>val)         # ... sets args[1] = {opt=>val}
c(1, 2, :opt=>val) {...}   # ... sets args[1] = {opt=>val} and block ={...}

To pull a/an options has off of the argument list:

opt = args.last.is_a?(Hash) ? args.pop : {} # Assigns last (option) hash as opt hash

To send a operation to a given block:

if block_given?            # checks for a called block 
  yield block *args        # calls the block, passing the arguments
end                        #

Exceptions

Rubies begin…rescue…end constuct is designed to capture errors and handle them. At a mimumum, all you need is the begin….rescue…end, the other clauses are for when you do need them.

begin                      # Starts a block to catch and possibly resolve exceptions
  raise "Message"          # Oh noes! Something unexptected happened, raise the error
rescue Exception => e      # Captures matching exception class, assigns to 'e' variable
  retry if tries < 3       # We can retry the block for a while, for timeouts, etc.
  puts "got #{e}"          #
rescue                     # Captures any other Exception
  puts "got exception"     #
else                       # This block is executed when there was no exception
  puts "no exception"      #
ensure                     # This block is ALWAYS executed, to close resources, etc.
  puts "always executed"   # For instance, close files, connections, etc.
end                        # Ends the block.

Throw and Catch are not for errors, but for execution out of bounds, an escape

catch(:symbolname) do      # Captures throw :symbolname, returns the payload thrown
  # complex multi-level loops and conditions
  throw :symbolname, expression # Get out of here! Jump to end of catch block
end                        # End of scope of throwable :symbolname 
x = catch(:x) { throw(:x, 23) }  # Assigns 23 to x if we get in a mess within the block