Ruby

“I always thought Smalltalk would beat Java. I just didn’t know it would be called ‘Ruby’ when it did”
- Kent Beck

The Ruby Language

Everything in Ruby is

Ruby is Line Oriented

Defining a Class and Instantiating an Object

class Person
  def initialize(name)
    @name = name
  end

  def say_hi
    puts "#{@name} says hi" 
  end
end

andreas = Person.new("Andreas")
andreas.say_hi

Class Inheritance

require 'person_class'

class Programmer < Person
  def initialize(name, favorite_ide)
    super(name)
    @favorite_ide = favorite_ide
  end

  # We are overriding say_hi in Person
  def say_hi
    super
    puts "Favorite IDE is #{@favorite_ide}" 
  end
end

peter = Programmer.new("Peter", "TextMate")
peter.say_hi

Getter and Setter Methods

class Person
  def initialize(name)
    self.name = name
  end
  def name
    @name
  end
  def name=(name)
    @name = name
  end
end

person = Person.new("Andreas")
puts person.name
person.name = "David" 
puts person.name

attr_accessor

class Person
  attr_accessor :name
  
  def initialize(name)
    self.name = name
  end
end

person = Person.new("Andreas")
puts person.name
person.name = "David" 
puts person.name

Variable/Method Ambiguity Gotcha

class Person
  attr_accessor :paid
  
  def initialize
    @paid = false
  end
  
  def make_payment
    puts "making payment..." 
    paid = true
  end
end

person = Person.new
person.make_payment
puts "paid=#{person.paid}" 

Methods

Methods and Parenthesis


def square(number)
  number * number
end

square(2+2)*2 # => square(4)*2 = 32
square (2+2)*2 # => square(4*2) = 64

Method Visibility


class MyClass  
  def method1 # Methods are public by default
  end  
  # Protected methods can be invoked by any instance of the same class or a
  # subclass of MyClass
  protected  
  def method2
  end
  # Private methods can only be invoked within an instance of MyClass or a subclass
  # of MyClass. The receiver of a private method is always self.
  private  
  def method3
  end
end

Class Methods

class Person
  def self.class_method
    puts "class method invoked" 
  end

  class << self
    def class_method2; puts "class_method2"; end
    def class_method3; puts "class_method3"; end
  end
end

class << Person
  def class_method4; puts "class_method4"; end
end

# Invocation of class method
Person.class_method

Singleton Classes and Methods

require 'person'

# Every object has two classes: the class of which it is an instance, and a singleton class. Methods
# of the singleton class are called singleton methods and can only be invoked on that particular object.
andreas = Person.new("Andreas")
def andreas.andreas_says_hi
  "Andreas says hi" 
end
andreas.andreas_says_hi

# Class methods are singleton methods on the class
# object and can be defined like this:
def Person.count
  @@count ||= 0
end
puts Person.count

Naming Conventions

Boolean Expressions

Assignment

Idiom: Assignment with Boolean Expression

comment = Object.new
def comment.user
  Object.new
end

# Overly verbose:
user_id = nil
if comment
  if comment.user
    user_id = comment.user.object_id
  end
end

# Idiomatic:
user_id = comment && comment.user && comment.user.object_id

Modules

require 'person_class'
module HasAge
  attr_accessor :age
end
class Person
  include HasAge
end
peter = Person.new("Peter"); peter.age = 33; puts peter.age
module MyApp
  class Person
    attr_accessor :hometown
    def initialize(hometown)
      self.hometown = hometown
    end
  end
end
peter = MyApp::Person.new("Stockholm"); puts peter.hometown

Modules vs Classes

Everything is an Object

Constants

Introspection


andreas = Person.new("Andreas")
andreas.inspect # => #<Person:0x1cf34 @name="Andreas">

andreas.class # => Person
andreas.class.superclass # => Object
andreas.class.superclass.superclass # => nil

andreas.class.ancestors.join(", ") # Person, Object, Kernel

Person.instance_methods(false) # => say_hi
Kernel.methods.sort.join("\n") # => All methods in Kernel module

Arithmetic and Conversions


2.class == Fixnum
Fixnum.superclass == Integer
Integer.superclass == Numeric

3.0.class == Float
Float.superclass == Numeric

2/3 == 0
2/3.0 # => 0.6666667
2 + 3.0 == 5.0
"2".to_i + "3.0".to_f == 5.0

10000000000.class == Bignum
Bignum.superclass == Integer

2 + "3" # => TypeError: String can't be coerced into Fixnum

String Class


"ruby".upcase + " " + "rails".capitalize # => RUBY Rails

"time is: #{Time.now}\n second line" 

'no interpolation "here" #{Time.now}' # => no interpolation "here" #{Time.now}

"I" << "Go" << "Ruby" # => IGoRuby

%Q{"C" and "Java"} # => "C" and "Java" 

%q{single 'quoted'} # => single 'quoted'

<<-END
    A here
    document at #{Time.now}
END

Array Class

a = ["Ruby", 99, 3.14]
a[1] == 99
a << "Rails" 
['C', 'Java', 'Ruby'] == %w{C Java Ruby}
[1, 2, 3].map { |x| x**2 }.join(", ")
[1, 2, 3].select { |x| x % 2 == 0 }
[1, 2, 3].reject { |x| x < 3 }
[1, 2, 3].inject { |sum, i| sum + i }

[1, [2, 3]].flatten! # => [1, 2, 3]

%w{C Java Ruby}.include?("C") # => true

fruits = ['apple', 'banana']
fruits += ['apple'] unless fruits.include?('apple')
[1, 3, 5] & [1, 2, 3] # (intersection) => [1, 3]
[1, 3, 5] | [2, 4, 6] # (union) => [1, 3, 5, 2, 4, 6]
[1, 2, 3] - [2, 3] # (difference) => [1]

Hash Class

h = {:lang => 'Ruby', :framework => 'Rails'}
h[:lang] == 'Ruby'
h[:perl] == nil
puts h.inspect
env = {"USER" => "peter", "SHELL" => "/bin/bash"}
env.each {|k, v| puts "#{k}=#{v}" }

Symbols

# Symbols start with a colon
:action.class == Symbol
:action.to_s == "action" 
:action == "action".to_sym

# There is only one instance of every symbol
:action.equal?(:action) # => true
'action'.equal?('action') # => false

# Symbols are typically used as keys in hashes
my_hash = {:controller => "home", :action => "index"}

More About Methods

Range Class

# Two dots is inclusive, i.e. 1 to 5
(1..5).each { |x| puts x**2 } 

# Three dots excludes the last item, i.e. 1 to 4
(1...5).each { |x| puts x }

(1..3).to_a == [1, 2, 3]

Structs

Rating = Struct.new(:name, :ratings) 
rating = Rating.new("Rails", [ 10, 10, 9.5, 10 ]) 

puts rating.name
puts rating.ratings

Exceptions

begin 
  raise(ArgumentError, "No file_name provided") if !file_name
  content = load_blog_data(file_name)
  raise "Content is nil" if !content 
rescue BlogDataNotFound 
  STDERR.puts "File #{file_name} not found" 
rescue BlogDataConnectError
  @connect_tries ||= 1
  @connect_tries += 1
  retry if @connect_tries < 3 
  STDERR.puts "Invalid blog data in #{file_name}" 
rescue Exception => exc 
  STDERR.puts "Error loading #{file_name}: #{exc.message}" 
  raise
end

if, unless, and the ? Operator

message = if count > 10
            "Try again" 
          elsif tries == 3
            "You lose" 
          else
            "Enter command" 
          end

raise "Unauthorized" if !current_user.admin?
raise "Unauthorized" unless current_user.admin?

status = input > 10 ? "Number too big" : "ok" 

Iterators: while, until, for, break, and next

while count < 100
  puts count
  count += 1
end

payment.make_request while (payment.failure? and payment.tries < 3)

for user in @users
  next if user.admin?
  if user.paid?
    break
  end
end

until count > 5
  puts count
  count += 1
end

puts(count += 1) until count > 5

Case

x = 11
case x
when 0
when 1, 2..5
  puts "Second branch" 
when 6..10
  puts "Third branch" 
when *[11, 12]
  puts "Fourth branch" 
when String: puts "Fifth branch" 
when /\d+\.\d+/
  puts "Sixth branch" 
when x.to_s.downcase == "peter" 
  puts "Seventh branch" 
else
  puts "Eight branch" 
end

Blocks – Usage Examples

# Iteration
[1, 2, 3].each { |item| puts item }

# Resource Management
File.open("file.txt", "w") do |file|
  file.puts "foobar" 
end

# Callbacks
widget.on_button_press do
  puts "Got button press" 
end

# Convention: one-line blocks use {...} and multiline
# blocks use do...end

Common String Operations

"  ".empty? == true
IO.read(__FILE__).each_with_index { |line, i| puts "#{i}: #{line}" }
"abc".scan(/./).each { |char| char.upcase }
"we split words".split.join(", ")
"    strip space   ".strip
sprintf("value of %s is %.2f", "PI", 3.1416)
"I Go Ruby"[2, 2] == "I Go Ruby"[2..3] # => "Go" 

The dup Method and Method Arguments

# Methods that change their receiver end with an exclamation mark by convention.
# If you need to invoke an exclamation mark method on a method argument and you want
# to avoid the object from being changed, you can duplicate the object first
# with the Object#dup method. Core classes such as String, Hash, and Array all have
# meaningful implementations of the dup method. Here is an example from Rails:

class ActiveRecord::Base
  def attributes=(new_attributes)
    return if new_attributes.nil?
    attributes = new_attributes.dup # duplicate argument to avoid changing it
    attributes.stringify_keys! # modify the duplicated object
    # ... method continues here
  end
end

Regular Expressions

"Ruby" =~ /^(ruby|python)$/i
"Go\nRuby" =~ /Go\s+(\w+)/m; $1 == "Ruby" 
"I Go Ruby" =~ /go/i; $& == "Go"; $` == "I "; $' == " Ruby" 
pattern = "."; Regexp.new(Regexp.escape(pattern))
"I Go Ruby"[/(go)/i, 1] == "Go" 
puts "I Go Ruby".gsub(%r{Ruby}, '\0 or I go bananas')
"I Go Ruby".gsub(/ruby/i) { |lang| lang.upcase }
line = "I Go Ruby" 
m, who, verb, what = *line.match(/^(\w+)\s+(\w+)\s+(\w+)$/)
# \s, \d, [0-9], \w - space, digit, and word character classes
# ?, *, +, {m, n}, {m,}, {m} - repetition

Invoking External Programs

system("ls -l")
# $? is a predefined variable with the exit status
puts $?.exitstatus if !$?.success?
# The back ticks "`" return the output of the external program
standard_out = `ls -l`

RDoc and Option Parsing

#!/usr/bin/env ruby
# == Synopsis
# This script takes photographs living locally on my desktop or laptop
# and publishes them to my homepage at http://marklunds.com.
#
# == Usage
#
# Copy config file publish-photos.yml.template to publish-photos.yml and edit as appropriate.
# ruby publish-photos [ -h | --help ] <photo_dir1> ... <photo_dirN>

# Load the Rails environment
require File.dirname(__FILE__) + '/../config/environment'
require 'optparse'
require 'rdoc/usage'

opts = OptionParser.new
opts.on("-h", "--help") { RDoc::usage('usage') }
opts.on("-q", "--quiet") { Log::Logger.verbose = false }
opts.parse!(ARGV) rescue RDoc::usage('usage')

Photos::Publisher(ARGV)

Ruby on the Command Line


# Query and replace
ruby -pi.bak -e "gsub(/Perl/, 'Ruby')" *.txt

# Grep
ruby -n -e "print if /Ruby/" *.txt
ruby -e "puts ARGF.grep(/Ruby/)" *.txt

Open Class Definitions and Method Aliasing

class Peter
  def say_hi
    puts "Hi" 
  end
end

class Peter
  alias_method :say_hi_orig, :say_hi
  def say_hi
    puts "Before say hi" 
    say_hi_orig
    puts "After say hi" 
  end
end

Peter.new.say_hi

Core Classes are Also Open

class Integer
  def even?
    (self % 2) == 0
  end
end

p (1..10).select { |n| n.even? } # => [2, 4, 6, 8, 10]

method_missing: A VCR

class VCR
  def initialize
    @messages = []
  end

  def method_missing(method, *args, &block)
    @messages << [method, args, block]
  end

  def play_back_to(obj)
    @messages.each do |method, args, block|
      obj.send(method, *args, &block)
    end
  end
end

Using the VCR

require 'vcr'

vcr = VCR.new
vcr.gsub! /Ruby/, "Crazy" 
vcr.upcase!
object = "I Go Ruby" 
vcr.play_back_to(object)
puts object

const_missing – for Auto Loading Classes

def Object.const_missing(name) 
  @looked_for ||= {} 
  str_name = name.to_s 
  raise "Class not found: #{name}" if @looked_for[str_name] 
  @looked_for[str_name] = 1 
  file = str_name.downcase 
  require file 
  klass = const_get(name) 
  return klass if klass 
  raise "Class not found: #{name}" 
end

eval and binding

def evaluate_code(code, binding)
  a = 2
  eval code, binding
end

a = 1
evaluate_code("puts a", binding) # => 1

instance_eval

require 'person_class'
andreas = Person.new("Andreas")
puts andreas.instance_eval { @name } # => Andreas

class_eval and module_eval

class Person
  def self.add_method(method)
    class_eval %Q{
      def #{method}
        puts "method #{method} invoked" 
      end
    }
  end    

  add_method(:say_hi)
end

Person.new.say_hi

define_method

class Array
  {:second => 1, :third => 2}.each do |method,element|
    define_method(method) do
      self[element]
    end
  end
end

array = %w(A B C)
puts array.first # => A
puts array.second # => B
puts array.third # => C

Object Space

ObjectSpace.each_object(Numeric) { |n| puts n }

Class Reflection

# Using Class#superclass
klass = Fixnum
begin
  print klass
  klass = klass.superclass
  print " < " if klass
end while klass
# => Fixnum < Integer < Numeric < Object

# Using Class#ancestors
Fixnum.ancestors
# => Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel

# Inspecting methods and variables
Fixnum.public_instance_methods(false)
Fixnum.class_variables
Fixnum.constants
1.instance_variables

System Hooks: Class#inherited

class A
  @@subclasses = {}  
  # Invoked when a new class is created that extends this class
  def self.inherited(child)
    puts "inherited" 
    @@subclasses[self] ||= []
    @@subclasses[self] << child
  end
  
  def self.subclasses
    @@subclasses[self]
  end
end

class B < A
end

puts A.subclasses

Ruby Load Path and Auto Loading in Rails