Home

About

Programming Ruby notes

19 May 2015

Chapter 1 Getting started

RVM: Ruby Version Manager, lets you have multiple independent Ruby installations on the same machine.

irb: Interactive Ruby

$ irb
2.0.0 :001 > def sum(n1, n2)
2.0.0 :002?>     n1+n2
2.0.0 :003?>     end
=> nil

Ruby Documentation: RDoc and ri

ri ClassName

$ ri GC

Chapter 2 Ruby.new

puts "gin joint".length	# 9
puts "Rick".index("c")	# 2
puts 42.even?		# true

def say_goodnight(name)	# indentation is not significant; parentheses are optional
	result = "Good night, " + name	# equivalent: result = "Good night, #{name}"
	return result
end

puts say_goodnight("John-Boy")	# equivalent: puts(say_goodnight("John-Boy"))
puts say_goodnight("Mary-Ellen")

The value returned by a Ruby method is the value of the last expression evaluated

def say_goodnight(name)
	"Good night, #{name.capitalize}"
end
puts say_goodnight('ma')	# Good night, Ma

Arrays and Hashes

a = [1, 'cat', 3.14]
puts "The first element is #{a[0]}"

a[2] = nil
puts "The array is now #{a.inspect}"	# [1, 'cat', nil]

a = %w{ant bee cat dog elk}
a[0] 	# "ant"
a[3] 	# "dog"

inst_section = {
	'cello' => 'string',
	'clarinet' => 'woodwind',
	'drum'	=> 'percussion',
	'oboe'	=> 'woodwind',
	'trumpet'	=> 'brass',
	'violin'	=> 'string'
}

p inst_section['oboe']	# "woodwind"
p inst_section['basson']	# nil

histogram = Hash.new(0)
histogram['ruby']	# 0
histogram['ruby'] = histogram['ruby']+1

Symbols

Symbols are simply constant names that you don’t have to predeclare and that are guaranteed to be unique. A symbol literal starts with a colon and is normally followed by some kind of name

inst_section = {
	:cello => 'string',
	:clarinet => 'woodwind',
	:drum	=> 'percussion',
	:oboe	=> 'woodwind',
	:trumpet	=> 'brass',
	:violin	=> 'string'
}

symbols are so frequently used as hash keys that Ruby has a short syntax:

inst_section = {
	cello:  'string',
	clarinet:  'woodwind',
	drum:	 'percussion',
	oboe:	 'woodwind',
	trumpet:	 'brass',
	violin:	 'string'
}

p inst_section[:cello]

Control Structures

Most statements in Ruby return a value, which means you can use them as conditions.

while line = gets	# gets returns the next line from the standard input stream or nil when the end of the file is reached.
	puts line.downcase
end

statement modifiers

if radiation > 3000
	puts "Danger, Will Robinson"
end

# equivalent:
puts "Danger, Will Robinson" if radiation > 3000

square = 4 
while square < 1000
	square = square*square
end

# equivalent:
square = square*square while square < 1000

Regular Expression

/Perl.*Python/	# Perl, zero or more other chars, then Python
/Perl\s+Python/	# Perl, whitespace characters, then Python

Blocks and Iterators

code blocks, which are chunks of code you can associate with method invocations, almost as if they were parameters.

def call_block
	puts "Start of method"
	yield
	yield
	puts "End of method"
end

call_block { puts "In the block" }

# produces:
# Start of method
# In the block
# In the block
# End of method

def who_says_what
	yield("Dave", "hello")
	yield("Andy", "goodbye")
end

who_says_what { |person, phrase| puts "#{person} says #{phrase}"}

# produce:
# Dave says hello
# Andy says goodbye

[ 'cat', 'dog', 'horse' ].each {|name| print name, " "}
5.times { put "*" }
3.upto(6) { |i| print i }
('a'..'e').each { |char| print char }
puts	# cat dog horse *****3456abcde

Reading and Writing

while line = gets	# gets returns nil when it reaches the end of input
	print line
end

Command-line Arguments

puts "You gave #{ARGV.size} arguments"
p ARGV

Chapter 3 Classes, Objects, and Variables

class BookInStock
	attr_reader :isbn, :price	# create accessor methods
	attr_accessor :price	# if you want both a reader and a writer for a given attribute
	def initialize(isbn, price)
		@isbn = isbn	# instance variable
		@price = Float(price)
	end
	def to_s
		"ISBN: #{@isbn}, price: #{@price}"
	end

	def price_in_cents
		Integer(price*100+0.5)
	end

	def price_in_cents=(cents)	# make this "virtual attribute" writable
		@price = cents/100.0
	end

end

b2 = BookInStock.new("isbn2", 3.14)
p b2	# <BookInStock:0x007fac... @isbn="isbn2", @price=3.14>
puts b2	# ISBN: isbn2, price: 3.14

puts "Price in cents = #{b2.price_in_cents}"	# Price in cents = 314
b2.price_in_cents = 1234
puts "Price = #{b2.price}"	# Price = 12.34
puts "Price in cents = #{b2.price_in_cents}"	# Price in cents = 1234

## csv_reader.rb
require 'csv'
require_relative 'book_in_stock'

class CsvReader
	def initialize
		@books_in_stock = []
	end

	def read_in_csv_data(csv_file_name)
		CSV.foreach(csv_file_name, header:true) do |row|	# headers:true option tells the library to parse the first line of the file as teh names of the columns
			@books_in_stock << BookInStock.new(row["ISBN"], row["Price"])
		end
	end

	def total_value_in_stock
		sum = 0.0
		@book_in_stock.each {|book| sum += book.price}
		sum
	end
end

## stock_stats.rb
require_relative 'csv_reader'

reader = CsvReader.new

ARGV.each do |csv_file_name|
	STDERR.puts "Processing #{csv_file_name}"
	reader.read_in_csv_data(csv_file_name)
end

puts "Total value= #{reader.total_value_in_stock}"

Specifying Access Control

Class MyClass
	def method1	# default is 'public'
	#...
	end

	protected	# subsequent methods will be 'protected'
	def method2	
	#...
	end

	private
	def method3	# subsequent methods will be 'private'
	#...
	end

	public		# subsequent methods will be 'public'
	def method4
	#...
	end
end

# Alternatively,

Class MyClass
	def method1
	end
	def method2
	end
	def method3
	end
	def method4
	end
	#...

	public: method1, method4
	protected: method2
	private: method3
end

Variables

person = "Tim"
puts "The object in 'person' is a #{person.class}"	# The object in 'person' is a String
puts "The object has an id of #{person.object_id}"
puts "and a value of #{person}"	# and a value of 'Tim'

A variable is simply a reference to an object.

person1 = "Tim"
person2 = person1
person1[0] = 'J'

puts "#{person1}"	# Jim
puts "#{person2}"	# Jim

person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
puts "#{person1}"	# Jim
puts "#{person2}"	# Tim

person1 = "Tim"
person2 = person1
person1.freeze	# prevent modifications to the object
person2[0] = "J"	# raise a RuntimeError exception

Chapter 4 Containers, Blocks and Iterators

Arrays

a = [3.14, "pie", 99]
a.class # => Array
a.length # => 3
b = Array.new

a = [1,3,5,7,9]
a[1,3] # => [3, 5, 7]  ([start, count])
a[-3, 2] # => [5, 7]

a[1..3] #=> [3,5,7]
a[1...3] # => [3, 5]	([start..end] and [start...end))

a = [1,3,5,7,9]
a[3] = [9,8]	#=> [1,3,5,[9,8],7,9]
a[1,1] = [9,8,7] # => [1,9,8,7, 5,[9,8],7,9]

stack = []
stack.push "red"
stack.push "green"
stack	# => ["red", "green"]
stack.pop # => "green"

queue = []
queue.push "red"
queue.push "green"
queue.shift	# => "red"

Example: Word Frenquency

## words_from_string.rb
def words_from_string(string)
	string.downcase.scan(/[\w']+/)	# lowercase, then matches sequences containing "word characters" and single quotes
end

## count_frequency.rb
def count_frequency(word_list)
	counts = Hash.new(0)
	for word in word_list
		counts[word] += 1
	end
	counts
end

## entry point
require_relative "words_from_string.rb"
require_relative "count_frequency.rb"

raw_text = %{the problem ...}

word_list = words_from_string(raw_text)
counts = count_frequency(word_list)
sorted = counts.sort_by{|word, count| count}
top_five = sorted.last(5)

## Unit test
require_relative 'words_from_string'
require 'test/unit'

class TestWordsFromString < Test::Unit::TestCase
	def test_single_word	# any methods whose names start with "test" are automatically run by the testing framework
		assert_equal(["cat"], words_from_string(" cat "))
	end
	def test_many_words
		assert_equal(["the","cat","sat","on","the","mat"], words_from_string("the cat sat on the mat"))
	end
end

Blocks and Iterators

top_five.each do |word, count|	# the method "each" is an iterator that invokes a block of code repeatedly
	puts "#{word}: #{count}"
end

# Alternatively
puts top_five.map {|word, count| "#{word}: #{count}"}

A Block is a chunk of code enclosed between either braces or the keywords “do” and “end”. You can think of a block as being somewhat like the body of an anonymous method. Just like a method, the block can take parameters

some_array.each {|value| puts value*3}  # parameter: value

sum = 0
other_array.each do |value|	# parameter: value
	sum += value
	puts value/sum
end

A Ruby iterator is simply a method that can invoke a block of code.

Within the method, the block may be invoked, almost as if it were a method itself, using the yield statement. Whenever a yield is executed, it invokes the code in the block. When the block exits, control picks back up immediately after the yield.

you can pass parameters to them and receive values from them.

def fib_up_to(max)
	i1, i2 = 1,1
	while i1 <= max
		yield i1
		i1, i2 = i2, i1+i2
	end
end

fib_up_to(1000) { |f| print f, " " }	# 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

iterator “find” returns the first corresponding element if the block returns true

[1,3,5,7,9].find { |v| v*v > 30 }	# 7

iterator “collect” takes each element from the collection and passes it to the block

["H", "A", "L"].collect{|x| x.succ}	# ["I", "B", "M"]

with_index: like “enumerate” in Python

f = File.open('testfile')
f.each.with_index do |line, index|
	puts "Line #{index} is: #{line}
end
f.close

# ouput:
# Line 0 is: xxx
# Line 1 is: xxx

inject

[1,3,5,7].inject {|sum, element| sum+element}	# 16
[1,3,5,7].inject {|product, element| product*element}	# 105

[1,3,5,7].inject(:+)	# 16
[1,3,5,7].inject(:*)	# 105

Enumerators – External Iterators

a = [1,3,"cat"]
h = {dog: "canine", fox: "vulpine"}

enum_a = a.to_num
enum_h = h.to_num

enum_a.next # 1
enum_h.next # [:dog, "canine"]
enum_a.next # 3
enum_h.next # [:fox, "vulpine"]

short_enum = [1,2,3].to_enum
long_enum = ('a'...'z').to_enum

loop do
	puts "#{short_enum.next} - #{long_enum.next}"
end

# output
# 1 - a
# 2 - b
# 3 - c

the “Enumerable” module defines each_with_index. This invokes its host class’s each Method, returning successive values along with an index:

['a', 'b', 'c'].each_with_index {|item, index| result << [item, index]}
result	# [["a", 0], ["b", 1], ["c", 2]] 

result = []
"cat".each_char.with_index{|item, index| result << [item, index]}
result	# [["c", 0], ["a", 1], ["t", 2]]

Enumerators Are Generators and Filters, Lazy Enumerators in Ruby 2 ??

class File
	def self.open_and_process(*args)	# collect the actual parameters passed to the method into an array named args
		f = File.open(*args)
		yield f
		f.close()
	end
end

File.open_and_process("testfile", "r") do |file|
	while line = file.gets
		puts line
	end
end

class File
	def self.my_open(*args)
		result = file = File.new(*args)
		# If there's a block, pass in the file and close the file when it returns
		if block_given?	# returns true if a block is associated with the current method
			result = yield file
			file.close
		end
		result
	end
end 	

Blocks Can Be Objects

If the last parameter in a method definition is prefixed with an ampersand (&action), Ruby looks for a code block whenever that method is called. That code block is converted to an object of class Proc and assigned to the parameter.

class ProcExample
	def pass_in_block(&action)
		@store_proc = action
	end
	def use_proc(parameter)
		@store_proc.call(parameter)
	end
end

eg = ProcExample.new
eg.pass_in_block { |param| puts "The parameter is #{param}" }
eg.use_proc(99)	# The parameter is 99

It’s a great way of implementing callbacks, dispatch tables and so on.

Blocks Can Be Closures

def n_times(thing)	# Even though that parameter is out of scope by the time the block is called, the parameter remains accessible to the block. This is called a closure
	lambda { |n| thing*n }
end

p1 = n_times(23)
p1.call(3)	# => 69
p1.call(4)	# => 92
p2 = n_times("Hello ")
p2.call(3)	# => "Hello Hello Hello "

def power_proc_generator
	value = 1
	lambda { value += value }
end

power_proc = power_proc_generator

puts power_proc.call
puts power_proc.call
puts power_proc.call

# produces:
# 2
# 4
# 8

An Alternative Notation

Ruby has another way of creating Proc objects. Rather than write this:

lambda { |params| ... }

you can now write the following:

->params { ... }
proc1 = ->arg {puts "In proc1 with #{arg}" }
proc2 = ->arg1, arg2 {puts "In proc2 with #{arg1} and #{arg2}"}
proc3 = ->(arg1, arg2) { puts "In proc3 with #{arg1} and #{arg2}"}

proc1.call "ant"
proc2.call "bee", "cat"
proc3.call "dog", "elk"


def my_if (condition, then_clause, else_clause)
	if condition
		then_clause.call
	else
		else_clause.call
	end
end

5.times do |val|
	my_if val<2,
	      -> {puts "#{val} is small"}
	      -> {puts "#{val} is big"}
end

# produces:
# 0 is small
# 1 is small
# 2 is big
# 3 is big
# 4 is big
# 5 is big

def my_while(cond, &body)
	while cond.call
		body.call
	end
end

a = 0

my_while -> {a<3} do
	puts a
	a += 1
end

# produces:
# 0
# 1
# 2

Block Parameter Lists

proc1 = lambda do |a, *b, &block|
	puts "a=#{a.inspect}"
	puts "b=#{b.inspect}"
	block.call
end
proc1.call(1,2,3,4) {puts "in block1"}

# produces:
# a=1
# b=[2,3,4]
# in block1

# using the new -> notation
proc2 = -> a,*b, &block do
	puts "a=#{a.inspect}"
	puts "b=#{b.inspect}"
	block.call
end

Chapter 5 Sharing Functionality: Inheritance, Modules, and Mixins

class Parent
	def say_hello
		puts "Hello from #{self}"
	end
end

## Subclass the parent
class Child < Parent
end

Child.superclass # => Parent
Parent.superclass # => Object
Object.superclass # => BasicObject
BasicObject.superclass # => nil

Mixins

A module can’t have instance, because a module isn’t a class. However, you can include a module within a class definition. When this happens, all the module’s instance methods are suddenly available as methods in the class as well. They get mixed in.

module Debug
	def who_am_i?
		"#{self.class.name} (id:#{self.object_id}): #{self.name}"
	end
end

class Phonograph
	include Debug
	attr_reader :name
	def initialize(name)
		@name = name
	end
	#...
end

class EightTrack
	include Debug
	attr_reader :name
	def initialize(name)
		@name = name
	end
	#...
end

ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.who_am_i?
et.who_am_i?

The Ruby include statement simply makes a reference to a module. If that module is in a separate file, you must use require to drag that file in before using include. Second, a Ruby include does not simply copy the module’s instance methods into the class. Instead, it makes a reference from the class to the included module. If multiple classes include that module, they’ll all point to the same thing.

class Person
	include Comparable
	attr_reader :name

	def initialize(name)
		@name = name
	end
	def to_s
		"#{@name}"
	end
	def <=>(other)	# Comparable assumes that any class that uses it defines the operator <=>; get six comparison (<,<=,==,>=,>) functions for free
		self.name <=> other.name
	end
end

p1 = Person.new("Matz")
p2 = Person.new("Guido")
p3 = Person.new("Larry")

if p1 > p2
	puts "#{p1.name}'s name > #{p2.name}'s name"
end

puts "Sorted list:"
puts [p1, p2, p3].sort

In general, a mixin that requires its own state is not a mixin – it should be written as a class

Resolving Ambiguous Method Names

Ruby looks first in the immediate class of an object, then in the mixins included into that class, and then in superclass and their mixins.

Chapter 6 Standard Types

Ruby includes support for rational and complex numbers

Rational(3,4) * Rational(2,3)	# => (1/2)
Rational("3/4") * Rational("2/3")	# => (1/2)

Complex(1,2) * Complex(3,4)	# => (-5+10i)
Complex("1+2i") * Complex("3+4i")	# => (-5+10i)

Strings that contain just digits are not automatically converted into numbers

3 4
5 6
7 8

some_file.each do |line|
	v1, v2 = line.split
	print v1+v2, " "
end	# 34 56 78

some_file.each do |line|
	v1, v2 = line.split
	print Integer(v1)+Integer(v2), " "
end	# 7 11 15

How Numbers Interact

22/7	# => 3
Complex::I * Complex::I	# => (-1+0i)

require 'mathn'
22/7	# => (22/7)
Complex::I * Complex::I	#=> -1

3.times	{ print "X " }
1.upto(5)	{ |i| print i, " " }
99.downto(95)	{ |i| print i, " " }
50.step(80, 5)	{ |i| print i, " " }	# X X X 1 2 3 4 5 99 98 97 96 95 50 55 60 65 70 75 80

10.downto(7).with_index { |num, index| puts "#{index}: #{num}" }
# 0: 10
# 1: 9
# 2: 8
# 3: 7

Strings

You can substitute the value of any Ruby code into a string using the sequence #{expr}

"Seconds/day: #{24*60*60}"	# => Seconds/day: 86400
"Safe level is #$SAFE"		# => Safe level is 0

Working with Strings

Song = Struct.new(:title, :name, :length)

File.open("songdata") do |song_file|
	songs = []
	
	song_file.each do |line|
		file, length, name, title = line.chomp.split(/|s*|||s*/)
		songs << Song.new(title, name, length)
	end
	
	puts songs[1]
end

"2:58".split(/:/)	# => ["2", "58"]

Song = Struct.new(:title, :name, :length)

File.open("songdata") do |song_file|
	songs = []
	
	song_file.each do |line|
		file, length, name, title = line.chomp.split(/|s*|||s*/)
		name.squeeze!(" ")
		mins, secs = length.scan(/\d+/)
	end
	
	puts songs[1]
end

Ranges

The two-dot form creates an inclusive range, and the three-dot form creates a range that excludes the specified high value

1..10
'a'..'z'
0..."cat".length

You can convert a range to an array using the to_a method and convert it to an Enumerator using to_enum

(1..10).to_a	# => [1,2,3,4,5,6,7,8,9,10]
('bar'..'bat').to_a	# => ["bar", "bas", "bat"]
enum = ('bar'..'bat').to_num
enum.next	# => "bar"
enum.next	# => "bas"

seeing whether some value falls within the interval represented by the range:

(1..10) === 5	# => true
(1..10) === 15	# => false
(1..10) === 3.14159	# => true
('a'..'j') === 'c'	# => true
('a'..'j') === 'z'	# => false

Chapter 7 Regular Expression

By far the most common is to write it between forward slashes. =~ returns the character offset into the string at which the match occured


/cat/ =~ "dog and cat"	# => 8
/cat/ =~ "catch"	# => 0
/cat/ =~ "Cat"		# => nil

str = "cat and dog"
if str =~ /cat/
	puts "There's a cat here somewhere"
end

File.foreach("testfile").with_index do |line, index|
	puts "#{index}: #{line}" if line !~ /on/	# !~ means does not match a string
end

# sub method replaces the matched substring with the replaced text

str = "Dog and Cat"
new_str = str.sub(/Cat/, "Gerbil")
puts "Let's go to the #{new_str} for a pint"

# To replace all matches, use gsub (the g stands for global)

str = "Dog and Cat"
new_str1 = str.gsub(/a/, "*")
puts "Using gsub: #{new_str1}"

# Both sub and gsub returns a new string. If you want to modify the original string, use the sub! and gsub! forms:

str = "now is the time"
str.sub!(/i/, "*")
puts str 

Matching Against Patterns

# $& receives the part of the string that was matched by the pattern
# $` receives the part of the string that preceded the match 
# $' receives the string after the match

def show_regexp(string, pattern)
	match = pattern.match(string)
	if match
		"#{match.pre_match}->#{match[0]}<-#{match.post_match}"
	else
		"no match"
	end
end

show_regexp('very interesting', /t/)	# => very in->t<-eresting
show_regexp('Fats Waller', /lle/)	# => Fats Wa->lle<-r
show_regexp('Fats Waller', /z/)		# => no match

Anchors

# ^ and $ match the beginning and the end of a line
# \A matches the beginning of a string,
# \z and \Z matches the end of a string (\Z matches the end of a string unless the string ends with \n)
# \b and \B match word boundaries and nonword boundaries, respectively

# repetition
# r* Matches zero or more occurrences of r
# r+ Matches one or more occurrences of r
# r? Matches zero or one occurrence of r
# r{m,n} Matches at least m and at most n occurrences of r
# r{m,} Matches at least m occurrences of r
# r{,n} Matches at most n occurrences of r
# r{m} Matches exactly m occurrences of r

Chapter 8 More About Methods

Methods that return a boolean result are often named with a trailing ?.

Methods that modify their receiver may be named with a trailing exclamation mark (!).

Our convention is to use parentheses when a method has arguments and omit them when it doesn’t

Ruby lets you specify default values for a method’s arguments

def cool_dude(arg1="Miles", arg2="Coltrane", arg3="Roach")
	"#{arg1}, #{arg2}, #{arg3}"
end

cool_dude	# => "Miles, Coltrane, Roach"
cool_dude("Bart")	# => "Bart, Coltrane, Roach"

Variable-Length Arguments List

def varargs(arg1, *rest)
	"arg1=#{arg1}. rest=#{rest.inspect};
end

varargs("one")	# arg1=one. rest=[]
varargs "one", "two", "three"	# arg1=one. rest=["two", "three"]

# If you care only about the first and the last parameters, you could define the method:
# def split_apart(first, *, last)
def split_apart(first, *splat, last)
	puts "First: #{first.inspect}, splat: #{splat.inspect}, last: #{last.inspect}"
end

split_apart(1,2)		# First: 1, splat: [], last: 2
split_apart(1,2,3,4)	# First: 1, splat: [2,3], last: 4 

if the last parameter in a method definition is prefixed with &, any associated block is converted to a Proc object, and that object is assigned to the parameter. This allows you to store the block for use later.

class TaxCalculator
	def initialize(name, &block)
		@name, @block = name, block
	end
	def get_tax(amount)
		"#@name on #{amount} = #{@block.call(amount)}"
	end
end

tc = TaxCalculator.new("Sales tax") { |amt| amt*0.075 }
tc.get_tax(100)		# => "Sales tax on 100 = 7.5"
tc.get_tax(250)		# => "Sales tax on 250 = 18.75"

Every method you call returns a value. The value of a method is the value of the last statement executed by the method.

Splat! Expanding Collections in Method Calls

def five(a, b, c, d, e)
	"I was passed #{a} #{b} #{c} #{d} #{e}"
end

five(1, 2, 3, 4, 5)		# " I was passed 1 2 3 4 5"
five(*['a', 'b'], 1, 2, 3)		# "I was passed a b 1 2 3"
five(*(10..14))			# " I was passed 10 11 12 13 14

Making Blocks More Dynamic

print "(t)imes or (p)lus: "
operator = gets
print "number: "
if operator =~ /^t/
		calc = lambda {|n| n*number}
else
		calc = lambda {|n| n+number}
end
# if the last argument to a method is preceded by an ampersand, Ruby assumes that it is a Proc object.
puts ((1..10).collect(&calc).join(", "))	 

# produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Keyword Argument List

# new in Ruby 2.0
def search(field, options)
	option = {duration: 120}.merge(options)
	if options.has_key?(:duration)
			duration = options[:duration]
			options.delete(:duration)
	end
	if options.has_key?(:genre)
			genre = options[:genre]
			options.delete(:genre)
	end
	fail "Invalid options: #{options.key.join(', ')}" unless options.empty?
end

# You can collect these extra hash arguments as a hash parameter--prefix one element of your argument list with two asterisk
def search(field, genre: nil, duration: 120, **rest)
	p [field, genre, duration, rest]
end

search(:title, duration: 432, star: 3, genre: "jazz", tempo: "slow")
# produces: [:title, "jazz", 432, {:stars=>3, :tempo=>"slow"}]

Chapter 9 Expressions

Redefine backquotes

alias old_backquote `
def ` (cmd)
	result = old_backquote(cmd)
	if $? != 0
			puts "*** Command #{cmd} failed: status = #{$?.exitstatus}"
	end
	result
end

print `ls -l /etc/passwd`
print `ls -l /etc/wibble`

Parallel Assignment

a, b = 1, 2
a, b = b, a	# swap

*a, b = 1, 2, 3, 4	# a=[1,2,3], b=4
c, *d, e = 1,2,3,4	# c = 1, d = [2,3], e=4
first, *, last = 1,2,3,4,5,6	# first=1, last=6

Conditional Execution


# and, or and not
nil && 99	# nil
false && 99	# false
"cat" && 99	# 99

nil || 99	# 99
false || 99	# 99
"cat" || 99	# "cat"

# defined? returns nil if its argument is not defined
defined? l	# "expression"
defined? Math::PI	# "constant"
defined? 42.abs		# "method"
defined? dummy		# "nil"

# if and unless

if artist == "Gillespie"	# "then" is optional if the statements are on multiple lines 
		handle = "Dizzy"
elsif artist == "Parker"
		handle = "Bird"
else
		handle = "Unknown"
end

# must separate the boolean expression from the following statements with "then"
if artist == "Gillespie" then handle = "Dizzy"
elsif artist == "Parker" then handle = "Bird"
else  handle = "Unknown"

# unless
unless duration > 180
	listen_intently
end

# C-style
cost = duration>180 ? 0.35 : 0.25

# case Expression
case line
when /title=(.*)/
	puts "Title is #$1"
when /track=(.*)/
	puts "Track is #$1"
end

Loops

# until
until play_list.duration > 60
	play_list.add(song_list.pop)
end

a = 1
a *= 2	while a < 100
a 		# => 128
a -= 10 unless a < 100
a		# => 98

# this is perl style. gets assigns the last line read to the global variable $_
# the ~ operator does a regular expression match against $_
# This kind of code is falling out of fashion in the Ruby community and may end up being removed from the language
file = File.open("ordinal")
while file.gets
		print if ~/third/ .. ~/fifth/
end

# $. contains the current input line number
File.foreach ("ordinal") do |line|
	if (($. == 1)||line=~/eig/) .. (($. == 3)||line=~ /nin/)
			print line
	end
end

# grep
File.open("ordinal").grep(/d$/) do |line|
	puts line
end

for i in File.open("ordinal").find_all {|line| line =~ /d$/}
	print i.chomp, " "
end

# As long as your class defines a sensible each method, you can use a for loop to traverse its objects:
class Periods
	def each
		yield "Classical"
		yield "Jazz"
		yield "Rock"
	end
end

periods = Periods.new
for genre in periods
		print genre, " "
end
# produces: Classical Jazz Rock

# break, redo and next
while line = gets
		next if line =~ /^\s*#/	# like continue in C
		break if line =~ /^END/	# like break in C
		redo if line.gsub!(/`(.*?)`/) {eval ($1)}	# repeat the current iteration
end

Variable Scope, Loops, and Blocks

x = "initial value"
y = "another value"
[1,2,3].each do |x|
	y = x+1
end
[x, y] # => ["initial value", 4]

Chapter 10 Exceptions, catch, and throw

require 'open-uri'
page = "podcasts"
file_name = "#{page}.html"
web_page = open("http://progprog.com/#{page}")
output = File.open(file_name, "w")

begin	# raise an exception in begin/end block 
	while line = web_page.gets
			output.puts line
	end
	output.close
rescue Exception	# handle exception
	STDERR.puts "Failed to download #{page}: #{$!}"	# place a reference to the associated exception object into $!
	output.close
	File.delete(file_name)
	raise	# reraise the exception in $!, passing on those you can't handle to higher levels
else	# executed only if no exceptions are raised 
	puts "Congratulations -- no errors!"
ensure
	# ensure is similar with final in C++, always will be executed
end

# give Ruby the name of a local variable to receive the matched exception
# more readable than using $!
begin 
	eval string
rescue SyntaxError, NameError => boom
	print "String doesn't compile: " + boom
rescue StandardError => bang
	print "Error running Script: " + bang
end

play it again

# first, connect to an SMTP server using EHLO command;
# if the connection attempt fails, sets the @esmtp to false and retries the connection
# If it fails a second time, the exception is raised up to the caller
begin 
	# First try an extended login. If it fails, fall back to a normal login
	if @esmtp then @command.ehlo(helodom)
			else   @command.helo(helodom)
	end
rescue ProtocolError
	if @esmtp then
			@esmtp = false
			retry
	else
			raise
	end
end

Raising Exceptions

raise "Missing name" if name.nil?

if i>=names.size
		raise IndexError, "#{i} >= size (#{names.size})"
end

raise ArgumentError, "Name too big", caller

Adding Information to Exeptions

class RetryException < RuntimeError
	attr :ok_to_retry
	def initialize(ok_to_retry)
		@ok_to_retry = ok_to_retry
	end
end

def read_data(socket)
	data = socket.read(512)
	if data.nil? 
			raise RetryException.new(true), "transient read error"
	end
	# .. normal processing
end 

begin 
	stuff = read_data(socket) 
	# .. process stuff
rescue RetryException => detail 
	retry if detail.ok_to_retry
	raise
end

Catch and throw


# if any of the lines of the file doesn't contain a valid word, we want to abandon the whole process
word_list = File.open("wordlist")
word_in_error = catch(:done) do
	result = []
	while line = word_list.gets
			word = line.chomp
			# the second parameter of throw is the return value of the catch 
			# without the second parameter to throw, the catch returns nil 
			throw(:done, word) unless word =~ /^\w+$/
			result << word 
	end
	puts result.reverse
end

if word_in_error
		puts "Failed: ' #{word_in_error}' found, but a word is expected"
end

# produces: '*wow*' found, but a word is expected

# the throw does not have to appear within the static scope of the catch
def prompt_and_get(prompt)
	print prompt
	res = readline.chomp
	throw :quit_requested if res == "!"
	res
end

catch :quit_requested do
	name = prompt_and_get("Name: ")
	age = prompt_and_get("Age: ")
	sex = prompt_and_get("Sex: ")
	# ..
end
	

Chapter 11 Basic Input and Output

# IO#each_byte invokes a block with the next 8-bit byte from the IO object
# chr converts an integer to the corresponding ASCII character
File.open("testfile") do |file|
	file.each_byte.with_index do |ch, index|
		print #{ch.chr}:#{ch} "
		break if index > 10
	end
end

# produces: T:84 h:104 i:105 s:115

# IO#each_line calls the block with each line from the file
File.open("testfile") do |file|
	file.each_line { |line| puts "Got #{line.dump}" }
end

# you can pass a parameter to each_line as the line separator
# in the following example, the line separator is "e" instead of "\n"
File.open("testfile") do |file|
	file.each_line("e") { |line| puts "Got #{line.dump}" }
end


IO.foreach("testfile") { |line| puts line }

# read into string
str = IO.read("testfile")
str.length 	# => 66
str[0,30]	# substring indexed from 0 to 30

# read into an array
arr = IO.readlines("testfile")
arr.length 	# => 4
arr[0]

# write to files
File.open("output.txt", "w") do |file|
	file.puts "Hello"
	file.puts "1+2= #{1+2}"
end

# Doing I/O with Strings
require 'stringio'

ip = StringIO.new("now is\nthe time\nto learn\nRuby!")
op = StringIO.new("", "w")
ip.each_line do |line|
	op.puts line.reverse
end
op.string	# reverse the string line by line

Talking to Networks

require 'socket'

client = TCPSocket.open('127.0.0.1', 'www')
client.send("OPTIONS /~dave/ HTTP/1.0\n\n", 0)
puts client.readlines
client.close

# higher level
require 'net/http'
http = Net::HTTP::new('progprog.com', 80)
response = http.get('book/ruby3/programming-ruby-1-9')

if response.message == "OK"
		puts response.body.scan(/<img alt=".*?" src="(.*?)"/m).uniq[0,3]
end

# higher level
require 'open-uri'

open('http://progprog.com') do |f|
	puts f.read.scan(/<img alt=".*?" src="(.*?)"/m).uniq[0,3]
end

# Parsing HTML
require 'open-uri'
page = open('http://progprog.com/titles/ruby3/programming-ruby-1-9').read
if page =~ %r{<title>(.*?)</title>}m
		puts "Title is #{$1.inspect}"
end

# use the popular Nokogiri lib
require 'open-uri'
require 'nokogiri'

doc = Nokogiri::HTML(open("http://progprog.com"))
puts "Page title is " + doc.xpath("//title").inner_html

# output the first paragraph in the div with an id="copyright"
puts doc.css('dvi#copyright p')

# output the second hyperlink in the site-links div using xpath and css
puts "nSecond hyperlink is"
puts doc.xpath('id("site-links")//a[2]')
puts doc.css('#site-links a:nth-of-type(2)')

Chapter 12 Fibers, Threads and Process

Fibers

Ruby’s fibers are just a very simple coroutine mechanism.

In the following code, the constructor for the Fiber class takes a block and returns a fiber object

Subsequently, we can call resume on the fiber object. This causes the block to start execution

words = Fiber.new do
	File.foreach("testfile") do |line|
		line.scan(/\w+/) do |word|
			Fiber.yield word.downcase
		end
	end
	nil
end

counts = Hash.new(0)
while word = words.resume
		counts[word] += 1
end
counts.keys.sort.each {|k| print "#{k}: #{counts[k]}"}	

Multithreading

Prior to Ruby 1.9, these were implemented as green thread–threads were switched within the interpreter. In Ruby 1.9, threading is now performed by the operating system.

It uses native operating system threads but operates only a single thread at a time. You’ll never see two threads in the same application running Ruby code truely concurrently.

require 'net/http'

pages = %w( www.rubycentral.org slashdot.org www.google.com)

threads = pages.map do | page_to_fetch|
	Thread.new (page_to_fetch) do |url|
		http = Net::HTTP.new(url, 80)
		print "Fetching: #{url}\n"
		resp = http.get('/')
		print "Got #{url}: {resp.message}\n"
	end
end
threads.each{|thr| thr.join}

Thread Variables

count = 0
threads = 10.times.map do |i|
	Thread.new do 
		sleep(rand(0.1))
		Thread.current[:mycount] = count
		count += 1
	end
end

threads.each { |t| t.join; print t[:mycount], ", " }
puts "count = #{count}"

Mutual Exclusion

sum = 0
mutex = Mutex.new
threads = 10.times.map do
	Thread.new do 
		100_000.times do
			mutex.lock	# one at a time
			new_value = sum+1
			print "#{new_value} " if new_value % 250_000 == 0
			sum = new_value
			mutex.unlock
		end
	end
end

threads.each(&:join)
puts "\nsum=#{sum}"

# Mutex#synchronize ensures that the mutex will get unlocked even if an exception is thrown while it is locked
sum = 0
mutex = Mutex.new
threads = 10.times.map do
	Thread.new do 
		100_000.times do
			mutex.synchronize do	
				new_value = sum+1
				print "#{new_value} " if new_value % 250_000 == 0
				sum = new_value
				mutex.unlock
			end
		end
	end
end

# Mutex#try_lock takes the lock if it can, but returns false if the lock is already taken.
# If you want to claim a lock if a mutex is currently unlocked, but you don't want to suspend the current thread if it isn't
rate_mutex = Mutex.new
exchange_rates = ExchangeRates.new
exchange_rates.update_from_online_feed

Thread.new do
	loop do
		sleep 3600
		rate_mutex.synchronize do 
			exchange_rates.update_from_online_feed
		end
	end
end

loop do
	print "Enter currency code and amount: "
	line = gets
	if rate_mutex.try_lock
			puts(exchange_rates.convert(line)) ensure rate_mutex.unlock
	else
			puts "Sorry, rates being updated. Try again in a minute"
	end
end

# If you are holding the lock on a mutex and you want to temporarily unlock it, allowing others to use it, 
# you can use Mutex#sleep
rate_mutex = Mutex.new
exchange_rates = ExchangeRates.new
exchange_rates.update_from_online_feed

Thread.new do
	rate_mutex.lock
	loop do
		rate_mutex.sleep 3600
		rate_mutex.synchronize do 
			exchange_rates.update_from_online_feed
		end
	end
end

loop do
	print "Enter currency code and amount: "
	line = gets
	if rate_mutex.try_lock
			puts(exchange_rates.convert(line)) ensure rate_mutex.unlock
	else
			puts "Sorry, rates being updated. Try again in a minute"
	end
end

Running Multiple Processes

# Spawn new Process, invoke system API, executes the command in a subprocess
system("tar xzf test.tgz")
`date`

# the popen method runs a command as a subprocess and connects that subprocess's standard input and 
# standard output to a Ruby IO object
pig = IO.popen("local/util/pig", "w+")
pig.puts "ice cream after they go to bed"
pig.close_write
puts pig.gets 

# Blocks and Subprocesses
IO.popen("date") {|f| puts "Date is #{f.gets}"}

Chapter 13 Unit Testing

require_relative 'romanbug'
require 'test/unit'

class TestRoman < Test::Unit::TestCase
	def test_simple
		assert_equal("i", Roman.new(1).to_s)
		assert_equal("ix", Roman.new(9).to_s)
	end
end

Chapter 14 When Trouble Strikes

Ruby Debugger

# command: ruby -r debug <debug-options> <programfile> <program-arguments>
$ ruby -r debug t.rb

Benchmark, time section of code

require 'benchmark'
include Benchmark

LOOP_COUNT = 1_000_000

bmbm(12) do |test|
	test.report("inline:") do
		LOOP_COUNT.times do |x|
			#nothing
		end
	end
	test.report("method":) do
		def method
			#nothing
		end
		LOOP_COUNT.times do |x|
			method
		end
	end
end

The profiler

You can add profiling to your code using the command-line option -r profile

Chapter 15 Ruby and Its World

Command-Line Arguments

If no filename is present on the command line or if the filename is a single hyphen (-), Ruby reads the program source from the standard input

# test.rb
ARGV.each {|arg| p arg}

# command line
$ ruby -w test.rb "Hello World" a1 1.6180

# output: "Hello World" "a1" "1.6180"

# ARGV[0] is the first argument to the program, not the program name
# program name is available in the global variable $0, which is aliased to $PROGRAM_NAME 

# ARGF
while line = gets
	printf "%d: %10s[%d] %s", ARGF.lineno, ARGF.filename, ARGF.file.lineno, line
end

$ ruby copy.rb testfile otherfile
1:  testfile[1] This is line one
2:  testfile[2] This is line two
3: otherfile[1] ANOTHER LINE ONE

In-place Editing

# reverse.rb
while line = gets
		puts line.chomp.reverse
end

# command line, testfile and otherfile would now have reversed lines,
# and that the original files would be available in testfile.bak and otherfile.bak
$ ruby -i.bak reverse.rb testfile otherfile

Environment Variables

You can access operating system environment variables using the predefined variable ENV

ENV['SHELL']
ENV['HOME']
ENV.keys.size

The Rake Build Tool

Ruby version of Make

# put the following code into a file called Rakefile
desc "Remove files whose names end with a tilde"
task :delete_unix_backups do
	files = Dir['*~']
	rm(files, verbose: true) unless files.empty?
end

# we can invoke this task from the command line
$ rake delete_unix_backups

# we can also compose tasks
desc "Remove Unix and Windows backup files"
task :delete_backups => [:delete_unix_backups, :delete_windows_backups] do
	puts "All backups deleted"
end

Chapter 17 Character Encoding

If the first line of the file is a comment (or the second line if the first line is a #! shebang line), Ruby scans it looking for the string coding:. Thus, to specify that a source file is in UTF-8 encoding, you can write this:

# coding: utf-8

As Ruby is just scanning for coding:, you could also write the following

# encoding: ascii

The String#bytes method is a convenient way to inspect the bytes in a string object. Notice that in the following code, the 16-bit codepoint is converted to a two-byte UTF-8 encoding:

# encoding: utf-8
"pi: \u03c0".bytes"	# => [112, 105, 58, 32, 207, 128]

You can force the external encoding associated with an I/O object when you open it

f = File.open("/etc/passwd", "r:ascii")
puts "File encoding is #{f.external_encoding"}"
line = f.gets
puts "Data encoding is #{line.encoding}"

# produces:
# File encoding is US-ASCII
# Data encoding is US-ASCII

If all your files are written with ISO-8859-1 encoding but you want your program to have to deal with their content as if it were UTF-8, you can use this:

ruby -E iso-8859-1:utf-8

# or you can specify just an internal encoding
ruby -E :utf-8

Chapter 18 Interactive Ruby Shell

load a file into irb

ruby 2.0 > load 'code/irb/fibonacci_sequence.rb'

Chapter 20 Ruby and the Web

CGI

require 'cgi'
puts CGI.escape("Nicholas Payton/Trumpet & Flugel Horn") # Nicholas+Payton%2FTrumpet+%26+Flugel+Horn

puts CGI.escapeHTML("a<100 && b>200")	# a&lt;100 &amp;&amp; b&gt;200

# escape only certain HTML elements within a string:
CGI.escapeElement('<hr><a href="/mp3">Click Here</a></hr>', 'A')
# (only <a...> is escaped)<hr>&lt;a href=&quot;/mp3&quot;&gt;Click Here&lt;/a&gt;<br>  

# unescape html:
# CGI.unescapeHTML(...)

Query Parameters

<html>
	<head>
		<title>Test Form</title>
	</head>
	<body>
		<p>I like Ruby because:</p>
		<form action="cgi-bin/survey.rb">
			<p>
				<input type="checkbox" name="reason" value="flexible" />
				It's flexible
			</p>
			<p>
				<input type="checkbox" name="reason" value="transparent" />
				It's transparent
			</p>
			<p>
				<input type="checkbox" name="reason" value="perlish" />
				It's like Perl
			</p>
			<p>
				<input type="checkbox" name="reason" value="fun" />
				It's fun
			</p>
			<p>
				Your name: <input type="text" name="name" />
			</p>
			<input type="submit" />
		</form>
	</body>
</html>

require 'cgi'
cgi = CGI.params # {"name"=>["Dave Thomas"], reason=["flexible", ..., "transparent", "fun"]
cgi.params['name']
cgi.params['reason']

erb and eruby

embed Ruby in an HTML document

Cookies

#!/usr/bin/ruby
require 'cgi'

COOKIE_NAME = 'chocolate chip'

cgi = CGI.new
values = cgi.cookies[COOKIE_NAME]

if values.empty?
	msg = "It looks as if you haven't visited recently"
else
	msg = "You last visited #{values[0]}"
end

cookie = CGI::Cookie.new(COOKIE_NAME, Time.now.to_s)
cookie.expires = Time.now + 30*24*3600	# 30 days
cgi.out("cookie" => cookie) {msg}

Sessions

require 'cgi'
require 'cgi/session'

cgi = CGI.new("html4")
sess = CGI::Session.new(cgi, "session_key" => "rubyweb", "prefix" => "web-session.")

if sess['lastaccess']
	msg = "<p>You were last here #{sess['lastaccess']}.</p>"
else
	msg = "<p>Looks like you haven't been here for a while</p>"
end

count = (sess["accesscount"] || 0).to_i
count += 1

msg << "<p>Number of visits: #{count}</p>"

sess["accesscount"] = count
sess["lastaccess"] = Time.now.to_s
sess.close

cgi.out {
		cgi.html {
				cgi.body {
						msg
				}
		}
}

web server

#!/usr/bin/ruby

require 'webrick'
include WEBrick

# set the document root to be the html/ subdirectory of the current directory
# s = HTTPServer.new(Port: 2000, DocumentRoot: File.join(Dir.pwd, "/html")
s = HTTPServer.new(Port: 2000)

class HelloServlet < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		res['Content-Type'] = "text/html"
		res.body = %{
				<html><body>
					<p>Hello. You're calling from a #{req['User-Agent']}</p>
					<p>I see paremeters: #{req.query.keys.join(', ')}</p>
				</body></html>}
	end
end

# mount a simple servlet at the location /hello
s.mount("/hello", HelloServlet)
# shut down on interrupts
trap("INT"){s.shutdown}
s.start	

Chapter 21 Ruby and Microsoft Windows

# create a new WIN32OLE client that launches a fresh copy of IE and visit its home page
# and navigate to a new page
require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = true
ie.gohome
ie.navigate("http://www.pragprog.com")
# properties
ie.Height = 300

build into OpenOffice suite to create a spreadsheet and populate some cells

class OOSpreadsheet
	def initialize
		mgr = WIN32OLE.new('com.sun.star.ServiceManager')
		desktop = mgr.createInstance("com.sun.star.ServiceManager")
		@doc = desktop.LoadComponentFromUrl("private:factory/scalc", "_blank", 0, [])
		@sheet = @doc.sheets[0]
	end

	def get_cell(row, col)
		@sheet.getCellByPosition(col, row, 0)
	end

	def get_cell_range(tl_row, tl_col, br_row, br_col)
		@sheet.getCellRangeByPosition(tl_row, tl_col, br_row, br_col, 0)
	end
end

spreadsheet = OOSpreadsheet.new
cell = spreadsheet.get_cell(1, 0)
cell.Value = 1234

cells = spreadsheet.get_cell_range(1,2,5,3)
cols = cells.Columns.count
rows = cells.Rows.count

cols.times do |col_no|
	rows.times do |row_no|
		cell = cells.getCellByPosition(col_no, row_no)
		cell.Value = (col_no+1)*(row_no+1)
	end
end

Events

require 'win32ole'

urls_visited = []
running = true

def default_handler(event, *args)
	case event
	when "BeforeNavigate"
		puts "Now Navigating to #{args[0]}..."
	end
end

ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = TRUE
ie.gohome
ev = WIN32OLE_EVENT.new(ie, 'DWebBrowserEvents')

ev.on_event {|*args| default_handler(*args)}
ev.on_event ("NavigateComplete") {|url| urls_visited << url}
ev.on_event ("Quit") do |*args|
	puts "IE has quit"
	puts "You Navigated to the following URLs: "
	urls_visited.each_with_index do |url, i|
		puts "(#{i+1}) #{url}"
	end
	running = false
end

# hang around processing messages
WIN32OLE_EVENT.message_loop while running

Chapter 22 The Ruby language

General Delimited Input (New in 2.0)

%i{ one digit#{1+1} three } #=> [:one, :"digit\#{1+1}", :three], array of symbols
%I{ one digit#{1+1} three } #=> [:one, :"digit2", :three]
%q{ one digit#{1+1} three } #=> " one digit\#{1+1} three "
%Q{ one digit#{1+1} three } #=> " one digit2 three "
%w{ one digit#{1+1} three } #=> [ "one", "digit\#{1+1}", "three"]
%W{ one digit#{1+1} three } #=> [ "one", "digit2", "three"]

# Just like Perl!!
%q/this is a string/
%q-string-
%q(a (nested) string)

String

# Adjacent single- and double-quoted strings are concatenated to form a single String object
'Con' "cat" 'en' "ate"	#=> "Concatenate"

Name

# an instance variable name starts with an @
# @name  @_  @size

# a class variable name starts with two @
# @@name  @@_  @@size
# Class variables are inherited by children but propagated upward if first defined in a child 

# global variables, and some special system variables, start with a $
# $params  $PROGRAM  $!  $_