|
Ruby Training NotesRussell Bateman |
I'm not super-serious about learning Ruby at this point, but my ability to Chef stands greatly impaired until I do learn enough of this language. Here, I'm using Learn Ruby the Hard Way (2011). What's below begins here, using the "Free book online". At least at first, I'm going to use vim and/or gvim under a bash console on Linux Mint 'cause that's how I roll.
The real reason I'm learning Ruby is because I've been using Chef for sometime and not knowing Ruby well enough has begun sticking in my craw.
Please see Ruby Notes: Links for my obligatory "useful links" section.
The ruby subdirectory featured in these notes should be available for download from here.
What's important to observe is that Ruby is installed and usable, that it has a version, and that an interactive Ruby shell, irb, is available for use in performing quick operations. This will be very useful for trying stuff out without having to create a file and execute it.
~/dev/ruby $ ruby -v ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-linux] ~/dev/ruby $ which irb /usr/bin/irb ~/dev/ruby $ irb irb(main):001:0> ^C
You'll be asked to do some things that presuppose the installation of Ruby's development package. You will not be told this and when you go to do them, they will simply fail. For help in installing the necessary, please see Appendix: Installing Ruby developer software.
Occasionally, I throw in some experiments such as ruby ex to see if Ruby's smart-enough to intuit the ubiquitous extension. Apparently, it is not.
The first real thing to learn is how to print stuff out to the console using the puts method.
|
~/dev/ruby $ ruby ex1.rb Hello World! Hello Again I like typing this. This is fun. Yay! Printing. I'd much rather you 'not'. I "said" do not touch this. |
ex1.rb: puts "Hello World!" puts "Hello Again" puts "I like typing this." puts "This is fun." puts 'Yay! Printing.' puts "I'd much rather you 'not'." puts 'I "said" do not touch this.' |
|
~/dev/ruby $ ruby ex2.rb I could have code like this. This will run. |
ex2.rb: # A comment, this is so you can read your program later. # Anything after the # is ignored by Ruby. puts "I could have code like this." # and the comment after is ignored # You can also use a comment to "disable" or comment out a piece of code: # puts "This won't run." puts "This will run." |
| + | plus |
| - | minus |
| / | slash |
| * | asterisk |
| % | percent |
| < | less-than |
| > | greater-than |
| <= | less-than-equal |
| >= | greater-than-equal |
There are two things we learn here. First, that numeric operations are just as we'd expect. Second, that after printing text out, we can add a comma and an expression to see its value printed out to the console. Notice, infuriatingly, that the comma forces a newline. If we try to thwart this, nastiness occurs:
~/dev/ruby $ irb irb(main):001:0> puts "Is it less or equal?", 5 <= -2 Is it less or equal? false => nil irb(main):002:0> puts "Is it less or equal?" 5 <= -2 SyntaxError: (irb):2: syntax error, unexpected tINTEGER, expecting $end puts "Is it less or equal?" 5 <= -2 ^ from /usr/bin/irb:12:in `<main>'
|
~/dev/ruby $ ruby ex3.rb I will now count my chickens: Hens 30 Roosters 97 Now I will count the eggs: 7 Is it true that 3 + 2 < 5 - 7? false What is 3 + 2? 5 What is 5 - 7? -2 Oh, that's why it's false. How about some more. Is it greater? true Is it greater or equal? true Is it less or equal? false |
ex3.rb: puts "I will now count my chickens:" puts "Hens", 25 + 30 / 6 puts "Roosters", 100 - 25 * 3 % 4 puts "Now I will count the eggs:" puts 3 + 2 + 1 - 5 + 4 % 2 - 1 / 4 + 6 puts "Is it true that 3 + 2 < 5 - 7?" puts 3 + 2 < 5 - 7 puts "What is 3 + 2?", 3 + 2 puts "What is 5 - 7?", 5 - 7 puts "Oh, that's why it's false." puts "How about some more." puts "Is it greater?", 5 > -2 puts "Is it greater or equal?", 5 >= -2 puts "Is it less or equal?", 5 <= -2 |
The main lesson here is that a Ruby variable is a simple identifier as in most other languages. It's used in expressions, statements, etc. too. Cleverly though, print statements can consume it as an expression similar to the bash shell, ant, etc. using the pattern:
#{variable}
|
~/dev/ruby $ ruby ex4.rb There are 100 cars available. There are only 30 drivers available. There will be 70 empty cars today. We can transport 120.0 people today. We have 90 passengers to carpool today. We need to put about 3 in each car. |
ex4.rb:
cars = 100
space_in_a_car = 4.0
drivers = 30
passengers = 90
cars_not_driven = cars - drivers
cars_driven = drivers
carpool_capacity = cars_driven * space_in_a_car
average_passengers_per_car = passengers / cars_driven
puts "There are #{cars} cars available."
puts "There are only #{drivers} drivers available."
puts "There will be #{cars_not_driven} empty cars today."
puts "We can transport #{carpool_capacity} people today."
puts "We have #{passengers} passengers to carpool today."
puts "We need to put about #{average_passengers_per_car} in each car."
|
Now we're starting to cook with gas when it comes to console output. We learn how to keep the value of a variable getting printed out on the same line and not require a comma. The way this is done looks a lot like C stdio.h. The following work:
~/dev/ruby $ irb irb(main):001:0> name=Russ irb(main):002:0> puts "My name is %s." % name My name is Russ. => nil
|
~/dev/ruby $ ruby ex5.rb Let's talk about Zed A. Shaw. He's 74 inches tall. He's 180 pounds heavy. Actually that's not too heavy. He's got Blue eyes and Brown hair. His teeth are usually White depending on the coffee. If I add 35, 74, and 180 I get 289. |
ex5.rb:
my_name = 'Zed A. Shaw'
my_age = 35 # not a lie
my_height = 74 # inches
my_weight = 180 # lbs
my_eyes = 'Blue'
my_teeth = 'White'
my_hair = 'Brown'
puts "Let's talk about %s." % my_name
puts "He's %d inches tall." % my_height
puts "He's %d pounds heavy." % my_weight
puts "Actually that's not too heavy."
puts "He's got %s eyes and %s hair." % [my_eyes, my_hair]
puts "His teeth are usually %s depending on the coffee." % my_teeth
# this line is tricky, try to get it exactly right
puts "If I add %d, %d, and %d I get %d." % [
my_age, my_height, my_weight, my_age + my_height + my_weight]
|
Here's where strings are formally introduced. Also introduced is the use of #{variable} in place of % variable which is a more convenient and macro or shell-like way of doing it. Also shown is Ruby string concatenation.
|
~/dev/ruby $ ruby ex6.rb There are 10 types of people. Those who know binary and those who don't. I said: There are 10 types of people.. I also said: 'Those who know binary and those who don't.'. Isn't that joke so funny?! false This is the left side of...a string with a right side. |
ex6.rb:
x = "There are #{10} types of people."
binary = "binary"
do_not = "don't"
y = "Those who know #{binary} and those who #{do_not}."
puts x
puts y
puts "I said: #{x}."
puts "I also said: '#{y}'."
hilarious = false
joke_evaluation = "Isn't that joke so funny?! #{hilarious}"
puts joke_evaluation
w = "This is the left side of..."
e = "a string with a right side."
puts w + e
|
You are meant to notice a number of things that haven't been explicitly introduced
like using a conversion specifier to plug in a string literal in the same way a
string variable was used previousy and "multiplying" output
(puts "." * 10). Also, you're encourged to examine the use of the
puts method to kick a newline.
|
~/dev/ruby $ ruby ex7.rb Mary had a little lamb. Its fleece was white as snow. And everywhere that Mary went. .......... CheeseBurger |
ex7.rb: puts "Mary had a little lamb." puts "Its fleece was white as %s." % 'snow' puts "And everywhere that Mary went." puts "." * 10 # what'd that do? end1 = "C" end2 = "h" end3 = "e" end4 = "e" end5 = "s" end6 = "e" end7 = "B" end8 = "u" end9 = "r" end10 = "g" end11 = "e" end12 = "r" # notice how we are using print instead of puts here. change it to puts # and see what happens. print end1 + end2 + end3 + end4 + end5 + end6 print end7 + end8 + end9 + end10 + end11 + end12 # this just is polite use of the terminal, try removing it puts |
Now we learn about printing arrays of things, numbers, strings, Booleans and strings.
|
~/dev/ruby $ ruby ex8.rb 1 2 3 4 one two three four true false false true %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s I had this thing. That you could type up right. But it didn't sing. So I said goodnight. |
ex8.rb:
formatter = "%s %s %s %s"
puts formatter % [1, 2, 3, 4]
puts formatter % ["one", "two", "three", "four"]
puts formatter % [true, false, false, true]
puts formatter % [formatter, formatter, formatter, formatter]
puts formatter % [
"I had this thing.",
"That you could type up right.",
"But it didn't sing.",
"So I said goodnight."
]
|
Now observe the artificial insertion of newlines into a string. Observe also the special PARAGRAPH-delimiting operator and the redirection operator (<<) used to "redirect" the paragraph to the puts method.
In fact, PARAGRAPH isn't exactly an operator since it could be anything as long as it's an identifier and completely brackets what's to go in it. We'll see that coming up in Exercise 10.
This construct is called a "here document". It's used to build strings from multiple lines. PARAGRAPH is only a made-up delimiter of no other significance.
|
~/dev/ruby $ ruby ex9.rb Here are the days: Mon Tue Wed Thu Fri Sat Sun Here are the months: Jan Feb Mar Apr May Jun Jul Aug There's something going on here. With the PARAGRAPH thing We'll be able to type as much as we like. Even 4 lines if we want, or 5, or 6. |
ex9.rb: # Here's some new strange stuff, remember type it exactly. days = "Mon Tue Wed Thu Fri Sat Sun" months = "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug" puts "Here are the days: ", days puts "Here are the months: ", months puts <<PARAGRAPH There's something going on here. With the PARAGRAPH thing We'll be able to type as much as we like. Even 4 lines if we want, or 5, or 6. PARAGRAPH |
This is mostly about escaping various characters like quotes that have important meaning in Ruby code into strings. And, there are other escape sequences too, like \t and probably everything you'd see in the C programming language for reference.
Remember PARAGRAPH? Well here we see there's nothing special about that string of characters.
|
~/dev/ruby $ ruby ex10.rb I'm tabbed in. I'm split on a line. I'm \ a \ cat. I'll do a list: * Cat food * Fishies * Catnip * Grass |
ex10.rb: tabby_cat = "\tI'm tabbed in." persian_cat = "I'm split\non a line." backslash_cat = "I'm \\ a \\ cat." fat_cat = <<MY_HEREDOC I'll do a list: \t* Cat food \t* Fishies \t* Catnip\n\t* Grass MY_HEREDOC puts tabby_cat puts persian_cat puts backslash_cat puts fat_cat |
Ruby's gets method, its name perhaps inspired (just as puts) by the C Standard Library functions, fetches a string from stdin. The exercises will more difficult to document from here on since they'll require interaction. This is, after all, the beginning of script-writing.
The chomp() method truncates while strip() trims.
We are careful to answer the questions exactly as done in the tutorial.
|
~/dev/ruby $ ruby ex11.rb How old are you? 35 How tall are you? 6'2" How much do you weigh? 180lbs So, you're 35 years old, 6'2" tall and 180lbs heavy. |
ex11.rb:
print "How old are you? "
age = gets.chomp()
print "How tall are you? "
height = gets.chomp()
print "How much do you weigh? "
weight = gets.chomp()
puts "So, you're #{age} years old, #{height} tall and #{weight} heavy."
|
We step away from what's really useful in script-writing for a moment to observe this topic. I immediately get an error and it's useful to admit that and find a solution.
|
~/dev/ruby $ ruby ex12.rb
/usr/lib/ruby/1.9.1/net/http.rb:762:in `initialize': getaddrinfo: \
Name or service not known (SocketError)
from /usr/lib/ruby/1.9.1/net/http.rb:762:in `open'
from /usr/lib/ruby/1.9.1/net/http.rb:762:in `block in connect'
from /usr/lib/ruby/1.9.1/timeout.rb:54:in `timeout'
from /usr/lib/ruby/1.9.1/timeout.rb:99:in `timeout'
from /usr/lib/ruby/1.9.1/net/http.rb:762:in `connect'
from /usr/lib/ruby/1.9.1/net/http.rb:755:in `do_start'
from /usr/lib/ruby/1.9.1/net/http.rb:744:in `start'
from /usr/lib/ruby/1.9.1/open-uri.rb:306:in `open_http'
from /usr/lib/ruby/1.9.1/open-uri.rb:775:in `buffer_open'
from /usr/lib/ruby/1.9.1/open-uri.rb:203:in `block in open_loop'
from /usr/lib/ruby/1.9.1/open-uri.rb:201:in `catch'
from /usr/lib/ruby/1.9.1/open-uri.rb:201:in `open_loop'
from /usr/lib/ruby/1.9.1/open-uri.rb:146:in `open_uri'
from /usr/lib/ruby/1.9.1/open-uri.rb:677:in `open'
from /usr/lib/ruby/1.9.1/open-uri.rb:33:in `open'
from ex12.rb:3:in `
|
ex12.rb:
require 'open-uri'
open("http://www.ruby-lang.org/en") do |f|
f.each_line {|line| p line}
puts f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
puts f.content_type # "text/html"
puts f.charset # "iso-8859-1"
puts f.content_encoding # []
puts f.last_modified # Thu Dec 05 02:45:02 UTC 2002
end
|
What went wrong? Well, I went to a browser to see if the URL was good in the first place. It was. Then I wondered how Ruby solved problems of proxy since I am behind a firewall. That was it. Here's what I did to make it work.
~/dev/ruby $ export http_proxy="http://web-proxy.austin.hp.com:8080" ~/dev/ruby $ ruby ex12.rb "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n" " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" " <head>\n" " <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n" . . .
Ruby is, above all, a scripting language (and less of a formal language in which to accomplish software engineering). Gathering arguments to a script (a Ruby file) is like bash, for instance, but potentially less cryptic as seen in this code. Instead of ${0} or $0 in Bourne/bash, the name of the script is #{$0}—I guess that's pretty cryptic, but somewhat infrequent. There are doubtless other ways to do this, but we're at the beginning of our Ruby apprentissage.
This stuff is really important for writing utility scripts, something we began in earnest back in lesson 11. The next lesson will be even more useful to this effort.
|
~/dev/ruby $ ruby ex13.rb first 2nd 3rd The script is called: ex13.rb Your first variable is: first Your second variable is: 2nd Your third variable is: 3rd The script is called: ex13.rb Your first variable is: cheese Your second variable is: apples Your third variable is: bread The script is called: ex13.rb Your first variable is: Zed Your second variable is: A. Your third variable is: Shaw The script is called: ex13.rb Your first variable is: Your second variable is: Your third variable is: |
ex13.rb:
first, second, third = ARGV
puts "The script is called: #{$0}"
puts "Your first variable is: #{first}"
puts "Your second variable is: #{second}"
puts "Your third variable is: #{third}"
|
Now we get oozingly involved in utility script-writing.
Mostly, this is to show how you must always use the chomp() method when writing interactive scripts because otherwise, you'll be including newlines in the answeres which will be at very least annoying.
Observe the use of MESSAGE. It's like PARAGRAPH and MY_HEREDOC we've already seen. These are not reserved keywords in Ruby.
It's important to realize that "simple" gets fetches stuff from the command line until all arguments have been consumed. If your caller added fantasy arguments, you would be getting them instead of asking the user to type in the answers to questions shown here. So, use STDIN.gets to avoid that.
Actually, this is a pretty cool thing: How many times have you written an interactive shell script, in which you had to ask questions and get answers, but wished you could be more at leisure to intermingle command-line arguments and interactive answers more naturally?
|
~/dev/ruby $ ruby ex14.rb Zed Hi Zed, I'm the ex14.rb script. I'd like to ask you a few questions. Do you like me Zed? > Yes, I wrote you Where do you live Zed? > The Rocky Mountains What kind of computer do you have? > I built it myself Alright, so you said Yes, I wrote you about liking me. You live in The Rocky Mountains. Not sure where that is. And you have a I built it myself computer. Nice. |
ex14.rb:
user = ARGV.first
prompt = '> '
puts "Hi #{user}, I'm the #{$0} script."
puts "I'd like to ask you a few questions."
puts "Do you like me #{user}?"
print prompt
likes = STDIN.gets.chomp()
puts "Where do you live #{user}?"
print prompt
lives = STDIN.gets.chomp()
puts "What kind of computer do you have?"
print prompt
computer = STDIN.gets.chomp()
puts <<MESSAGE
Alright, so you said #{likes} about liking me.
You live in #{lives}. Not sure where that is.
And you have a #{computer} computer. Nice.
MESSAGE
|
What was learned in the previous lesson was to prepare us for beginning to read files. An important construct is ARGV and STDIN, which we already learned. A new construct is the object, File.
Pay close attention to the rather complex, but very flexible way to handle prompting and input using puts, print and STDIN.gets.
|
~/dev/ruby $ ruby ex15.rb ex15_sample.txt
Here's your file: ex15_sample.txt
This is stuff I typed into a file.
It is really cool stuff.
Lots and lots of fun to have in here.
I'll also ask you to type it again:
> ex15_sample.txt
This is stuff I typed into a file.
It is really cool stuff.
Lots and lots of fun to have in here.
|
ex15.rb:
filename = ARGV.first
prompt = "> "
txt = File.open(filename)
puts "Here's your file: #{filename}"
puts txt.read()
close()
puts "I'll also ask you to type it again:"
print prompt
file_again = STDIN.gets.chomp()
txt_again = File.open(file_again)
puts txt_again.read()
again.close()
File ex15_sample.txt: This is stuff I typed into a file. It is really cool stuff. Lots and lots of fun to have in here. |
Notice that I added the close() calls as suggested.
It appeared we were to learn about ri, a sort of on-line documentor for Ruby. This does not work. Even assuming it needs a proxy was insufficient to get it to work. Please see the Appendix to get this working.
| Ruby file I/O | |
|---|---|
| open( filename, action ) | Opens filename for action {r|w|a}. |
| close() | Closes the file. |
| read() | Reads the file's contents. |
| readline() | Reads a single line from file. |
| truncate() | Empties the open file. |
| write() | Writes to the file if opened with w. |
| exists | Returns true/false that the file exists. |
|
~/dev/ruby $ touch test.txt We're going to erase test.txt. If you don't want that, hit CTRL-C (^C). If you do want that, hit RETURN. ? Opening the file... Truncating the file. Goodbye! Now I'm going to ask you for three lines. line 1: To all the people out there. line 2: I say I don't like my hair. line 3: I need to shave it off. I'm going to write these to the file. And finally, we close it.
~/dev/ruby $ cat test.txt To all the people out there. I say I don't like my hair. I need to shave it off. |
ex16.rb:
filename = ARGV.first
script = $0
puts "We're going to erase #{filename}."
puts "If you don't want that, hit CTRL-C (^C)."
puts "If you do want that, hit RETURN."
print "? "
STDIN.gets
puts "Opening the file..."
target = File.open(filename, 'w')
puts "Truncating the file. Goodbye!"
target.truncate(target.size)
puts "Now I'm going to ask you for three lines."
print "line 1: "; line1 = STDIN.gets.chomp()
print "line 2: "; line2 = STDIN.gets.chomp()
print "line 3: "; line3 = STDIN.gets.chomp()
puts "I'm going to write these to the file."
target.write(line1)
target.write("\n")
target.write(line2)
target.write("\n")
target.write(line3)
target.write("\n")
puts "And finally, we close it."
target.close()
|
We learn to read one file and copy it to another. We learn about File.exists and getting a string's length using the length method. Note that if output.close() isn't called, the file will not exist just as happens with any other file I/O library in any other language.
|
~/dev/ruby $ ruby ex17.rb test.txt copied.txt Copying from test.txt to copied.txt The input file is 81 bytes long Does the output file exist? false Ready, hit RETURN to continue, CTRL-C to abort. Alright, all done. ~/dev/ruby $ cat copied.txt To all the people out there. I say I don't like my hair. I need to shave it off. |
ex17.rb:
from_file, to_file = ARGV
script = $0
puts "Copying from #{from_file} to #{to_file}"
# we could do these two on one line too, how?
input = File.open(from_file)
indata = input.read()
puts "The input file is #{indata.length} bytes long"
puts "Does the output file exist? #{File.exists? to_file}"
puts "Ready, hit RETURN to continue, CTRL-C to abort."
STDIN.gets
output = File.open(to_file, 'w')
output.write(indata)
puts "Alright, all done."
output.close()
input.close()
|
...in Ruby. I have learned to say "method" under influence of Java, but Ruby was born concurrently with Java, so likely "function" is more correct and under the influence of the C programming language. It is said that "every Ruby function is a method and that methods are always called on objects". So, I'll retain the nomenclature of "method".
The "extra credit" section of this exercise is worth reading for the cultural comments about functions (or "methods").
|
~/dev/ruby $ ruby ex18.rb arg1: Zed, arg2: Shaw arg1: Zed, arg2: Shaw arg1: First! I got nothin'. |
ex18.rb:
# this one is like your scripts with argv
def puts_two(*args)
arg1, arg2 = args
puts "arg1: #{arg1}, arg2: #{arg2}"
end
# ok, that *args is actually pointless, we can just do this
def puts_two_again(arg1, arg2)
puts "arg1: #{arg1}, arg2: #{arg2}"
end
# this just takes one argument
def puts_one(arg1)
puts "arg1: #{arg1}"
end
# this one takes no arguments
def puts_none()
puts "I got nothin'."
end
puts_two("Zed","Shaw")
puts_two_again("Zed","Shaw")
puts_one("First!")
puts_none()
|
This exercise is mostly a waste of time if you've coded in some scripting languages which do not respect scope and real programming languages that do.
|
~/dev/ruby $ ruby ex19.rb We can just give the function numbers directly: You have 20 cheeses! You have 30 boxes of crackers! Man that's enough for a party! Get a blanket. OR, we can use variables from our script: You have 10 cheeses! You have 50 boxes of crackers! Man that's enough for a party! Get a blanket. We can even do math inside too: You have 30 cheeses! You have 11 boxes of crackers! Man that's enough for a party! Get a blanket. And we can combine the two, variables and math: You have 110 cheeses! You have 1050 boxes of crackers! Man that's enough for a party! Get a blanket. |
ex19.rb:
def cheese_and_crackers(cheese_count, boxes_of_crackers)
puts "You have #{cheese_count} cheeses!"
puts "You have #{boxes_of_crackers} boxes of crackers!"
puts "Man that's enough for a party!"
puts "Get a blanket."
puts # a blank line
end
puts "We can just give the function numbers directly:"
cheese_and_crackers(20, 30)
puts "OR, we can use variables from our script:"
amount_of_cheese = 10
amount_of_crackers = 50
cheese_and_crackers(amount_of_cheese, amount_of_crackers)
puts "We can even do math inside too:"
cheese_and_crackers(10 + 20, 5 + 6)
puts "And we can combine the two, variables and math:"
cheese_and_crackers(amount_of_cheese + 100, amount_of_crackers + 1000)
|
|
~/dev/ruby $ ruby ex20.rb First let's print the whole file: To all the people out there. I say I don't like my hair. I need to shave it off. Now let's rewind, kind of like a tape. Let's print three lines: 1 To all the people out there. 2 I say I don't like my hair. 3 I need to shave it off. |
ex20.rb:
input_file = ARGV[0]
def print_all(f)
puts f.read()
end
def rewind(f)
f.seek(0, IO::SEEK_SET)
end
def print_a_line(line_count, f)
puts "#{line_count} #{f.readline()}"
end
current_file = File.open(input_file)
puts "First let's print the whole file:"
puts # a blank line
print_all(current_file)
puts "Now let's rewind, kind of like a tape."
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
|
Just for grins, let's execute this script without arguments. It's important to begin learning how to debug problems.
ex20.rb:15:in `initialize': can't convert nil into String (TypeError) from ex20.rb:15:in `open' from ex20.rb:15:in `'
There's a genuine problem with this script, however, in that no blank line is printed after line 18, which the exercise shows, but didn't happen locally. "My kingdom for a debugger," as they say.
Mostly, this just demonstrates to the surprised that Ruby methods can return values for use by a caller. Again, no surprises.
|
~/dev/ruby $ ruby ex21.rb Let's do some math with just functions! ADDING 30 + 5 SUBTRACTING 78 - 4 MULTIPLYING 90 * 2 DIVIDING 100 / 2 Age: 35, Height: 74, Weight: 180, IQ: 50 Here is a puzzle. DIVIDING 50 / 2 MULTIPLYING 180 * 25 SUBTRACTING 74 - 4500 ADDING 35 + -4426 That becomes: -4391 Can you do it by hand? |
ex21.rb:
def add(a, b)
puts "ADDING #{a} + #{b}"
a + b
end
def subtract(a, b)
puts "SUBTRACTING #{a} - #{b}"
a - b
end
def multiply(a, b)
puts "MULTIPLYING #{a} * #{b}"
a * b
end
def divide(a, b)
puts "DIVIDING #{a} / #{b}"
a / b
end
puts "Let's do some math with just functions!"
age = add(30, 5)
height = subtract(78,4)
weight = multiply(90, 2)
iq = divide(100, 2)
puts "Age: #{age}, Height: #{height}, Weight: #{weight}, IQ: #{iq}"
# A puzzle for the extra credit, type it in anyway.
puts "Here is a puzzle."
what = add(age, subtract(height, multiply(weight, divide(iq, 2))))
puts "That becomes: #{what} Can you do it by hand?"
|
This is a non-coding assignment. I found the following aids...
...and kept adding to them as a "useful links" section here.
As I'm not in this for the hoop-jumping (though I greatly appreciate the approach), and this is another non-coding assignment, I'm not going to write anything up here. My Chef recipes, the ones I'm trying to understand and modify, are enough to satisfy this.
This exercise is getting close to winding up the first half and is only review of what's gone before.
|
~/dev/ruby $ ruby ex24.rb Let's practice everything. You'd need to know 'bout escapes with \ that do newlines and tabs. -------------- The lovely world with logic so firmly planted cannot discern the needs of love nor comprehend passion from intuition and requires an explanation where there is none. -------------- This should be five: 5 With a starting point of: 10000 We'd have 5000000 beans, 5000 jars, and 50 crates. We can also do that this way: We'd have 500000 beans, 500 jars, and 5 crates. |
ex24.rb:
puts "Let's practice everything."
puts "You\'d need to know \'bout escapes with \\ that do \n newlines and \t tabs."
poem = <<MULTI_LINE_STRING
\tThe lovely world
with logic so firmly planted
cannot discern \n the needs of love
nor comprehend passion from intuition
and requires an explanation
\n\t\twhere there is none.
MULTI_LINE_STRING
puts "--------------"
puts poem
puts "--------------"
five = 10 - 2 + 3 - 6
puts "This should be five: #{five}"
def secret_formula(started)
jelly_beans = started * 500
jars = jelly_beans / 1000
crates = jars / 100
return jelly_beans, jars, crates
end
start_point = 10000
beans, jars, crates = secret_formula(start_point)
puts "With a starting point of: #{start_point}"
puts "We'd have #{beans} beans, #{jars} jars, and #{crates} crates."
start_point = start_point / 10
puts "We can also do that this way:"
puts "We'd have %s beans, %s jars, and %s crates." % secret_formula(start_point)
|
Just functions. Initially, this is run only to guarantee there are no syntax errors. Then, a freer-form exercise in irb is worked through. That's actually pretty cool because it reintroduces Ruby require and then makes use of the methods/functions defined.
It's Friday evening and I'm in a hurry to blow this off, so I'm not going to record the interactive exercise. Okay, I lied; I'm going to do it here...
|
~/dev/ruby $ ruby ex25.rb (nothing) ~/dev/ruby $ irb irb(main):001:0> require './ex25.rb' => true irb(main):002:0> sentence = "All good things come to those who wait." => "All good things come to those who wait." irb(main):003:0> words = Ex25.break_words( sentence ) => ["All", "good", "things", "come", "to", "those", "who", "wait."] irb(main):004:0> sorted_words = Ex25.sort_words( words ) => ["All", "come", "good", "things", "those", "to", "wait.", "who"] irb(main):005:0> Ex25.print_first_word( words ) All => nil irb(main):006:0> Ex25.print_last_word( words ) wait. => nil irb(main):007:0> Ex25.wrods NoMethodError: undefined method `wrods' for Ex25:Module from (irb):7 from /usr/bin/irb:12:in `<main>' irb(main):008:0> words => ["good", "things", "come", "to", "those", "who"] irb(main):009:0> Ex25.print_first_word( sorted_words ) All => nil irb(main):010:0> Ex25.print_last_word( sorted_words ) who => nil irb(main):011:0> sorted_words => ["come", "good", "things", "those", "to", "wait."] irb(main):012:0> Ex25.sort_sentence( sentence ) => ["All", "come", "good", "things", "those", "to", "wait.", "who"] irb(main):013:0> Ex25.print_first_and_last( sentence ) All wait. => nil irb(main):014:0> Ex25.print_first_and_last_sorted( sentence ) All who => nil irb(main):015:0> |
ex25.rb:
module Ex25
def self.break_words(stuff)
# This function will break up words for us.
words = stuff.split(' ')
words
end
def self.sort_words(words)
# Sorts the words.
words.sort()
end
def self.print_first_word(words)
# Prints the first word and shifts the others down by one.
word = words.shift()
puts word
end
def self.print_last_word(words)
# Prints the last word after popping it off the end.
word = words.pop()
puts word
end
def self.sort_sentence(sentence)
# Takes in a full sentence and returns the sorted words.
words = break_words(sentence)
sort_words(words)
end
def self.print_first_and_last(sentence)
# Prints the first and last words of the sentence.
words = break_words(sentence)
print_first_word(words)
print_last_word(words)
end
def self.print_first_and_last_sorted(sentence)
# Sorts the words then prints the first and last one.
words = sort_sentence(sentence)
print_first_word(words)
print_last_word(words)
end
end
|
This exercise is sort of halfway through the tutorial with much more, better and harder stuff promised.
The link to the test code is http://ruby.learncodethehardway.org/book/exercise26.txt. The test is to comb through this source code in search of and to fix errors.
|
~/dev/ruby $ ruby ex26.rb (first go, let's get started...) ✔ ex26.rb:21: syntax error, unexpected tIDENTIFIER, expecting ')' puts word ^ ✔ex26.rb:67: syntax error, unexpected $undefined, expecting keyword_end jars = jelly_beans \ 1000 ^ ✔ex26.rb:73: syntax error, unexpected tEQ, expecting '=' beans, jars, crates == secret_formula(start-point) ^ ✔ex26.rb:76: syntax error, unexpected ')', expecting '=' ✔ex26.rb:84: syntax error, unexpected tIDENTIFIER, expecting ')' sentence = "All god\tthings come to those who weight." ^ ✔ex26.rb:98: syntax error, unexpected $end, expecting ')' (errors appearing, one at a time, after fixing the original set above:) ✔ex26.rb:73:in ` Note that two of the errors were created by bad syntax from the immediately previous (non-blank) line. The others were simple syntax errors like bad operators, parens for brackets, etc. In the end... ...I was unable to fix all the errors in the time I alloted to this exercise. My conclusion is, in part, don't let someone else write a pile of Ruby crap for you to debug. Instead, either write the crap yourself or write tests for everything you're going to write so that you don't inflict this madness on anyone following you. Of course, I did fix all the errors. I just wanted to plug test-driven development (TDD). |
ex26.rb:
# This function will break up words for us.
def break_words(stuff)
words = stuff.split(' ')
return words
end
# Sorts the words.
def sort_words(words)
return sorted(words)
end
# Prints the first word after popping it off.
def puts_first_word(words)
word = words.poop(0)
puts word
end
# Prints the last word after popping it off.
def puts_last_word(words)
word = words.pop(-1
puts word
end
# Takes in a full sentence and returns the sorted words.
def sort_sentence(sentence)
words = break_words(sentence)
return sort_words(words)
end
# Puts the first and last words of the sentence.
def puts_first_and_last(sentence)
words = break_words(sentence)
puts_first_word(words)
puts_last_word(words)
end
# Sorts the words then prints the first and last one.
def puts_first_and_last_sorted(sentence)
words = sort_sentence(sentence)
puts_first_word(words)
puts_last_word(words)
end
puts "Let's practice everything."
puts 'You\'d need to know \'bout escapes with \\ that do \n newlines and \t tabs.'
poem = <<POEM
\tThe lovely world
with logic so firmly planted
cannot discern \n the needs of love
nor comprehend passion from intuition
and requires an explantion
\n\t\twhere there is none.
POEM
puts "--------------"
puts poem
puts "--------------"
five = 10 - 2 + 3 - 5
puts "This should be five: %s" % five
def secret_formula(started)
jelly_beans = started * 500
jars = jelly_beans \ 1000
crates = jars / 100
return jelly_beans, jars, crates
end
start_point = 10000
beans, jars, crates == secret_formula(start-point)
puts "With a starting point of: %d" % start_point
puts "We'd have %d jeans, %d jars, and %d crates." % (beans, jars, crates)
start_point = start_point / 10
puts "We can also do that this way:"
puts "We'd have %d beans, %d jars, and %d crabapples." % secret_formula(start_pont
sentence = "All god\tthings come to those who weight."
words = ex25.break_words(sentence)
sorted_words = ex25.sort_words(words)
puts_first_word(words)
puts_last_word(words)
.puts_first_word(sorted_words)
puts_last_word(sorted_words)
sorted_words = ex25.sort_sentence(sentence)
prin sorted_words
puts_irst_and_last(sentence)
puts_first_a_last_sorted(senence)
|
The relational operators and other elements of logic in Ruby are:
No coded exercise. (Remember, this tutorial is trying to teach non programmers to code.)
More non-coding exercises on the elements of logic in Ruby.
|
~/dev/ruby $ ruby ex29.rb
Too many cats! The world is doomed!
The world is dry!
People are greater than or equal to dogs.
People are less than or equal to dogs.
People are dogs.
My parens work.
|
ex29.rb:
people = 20
cats = 30
dogs = 15
if people < cats
puts "Too many cats! The world is doomed!"
end
if people > cats
puts "Not many cats! The world is saved!"
end
if people < dogs
puts "The world is drooled on!"
end
if people > dogs
puts "The world is dry!"
end
dogs += 5
if people >= dogs
puts "People are greater than or equal to dogs."
end
if people <= dogs
puts "People are less than or equal to dogs."
end
if people == dogs
puts "People are dogs."
end
if( people == dogs ) added this just to see if it works.)
puts "My parens work."
end
|
|
~/dev/ruby $ ruby ex30.rb We should take the cars. Maybe we could take the buses. Alright, let's just take the buses. |
ex30.rb: people = 30 cars = 40 buses = 15 if cars > people puts "We should take the cars." elsif cars < people puts "We should not take the cars." else puts "We can't decide." end if buses > cars puts "That's too many buses." elsif buses < cars puts "Maybe we could take the buses." else puts "We still can't decide." end if people > buses puts "Alright, let's just take the buses." else puts "Fine, let's stay home then." end |
What's beginning to emerge is the observation that, like Bourne and bash Ruby makes no use of block delimiters such as begin/end or { }.
|
~/dev/ruby $ ruby ex31.rb You enter a dark room with two doors. Do you go through door #1 or door #2? > 1 There's a giant bear here eating a cheese cake. What do you do? 1. Take the cake. 2. Scream at the bear. > 2 The bear eats your legs off. Good job! ~/dev/ruby $ ruby ex31.rb You enter a dark room with two doors. Do you go through door #1 or door #2? > 1 There's a giant bear here eating a cheese cake. What do you do? 1. Take the cake. 2. Scream at the bear. > 1 The bear eats your face off. Good job! ~/dev/ruby $ ruby ex31.rb You enter a dark room with two doors. Do you go through door #1 or door #2? > 2 You stare into the endless abyss at Cthuhlu's retina. 1. Blueberries. 2. Yellow jacket clothespins. 3. Understanding revolvers yelling melodies. > 1 Your body survives powered by a mind of jello. Good job! ~/dev/ruby $ ruby ex31.rb You enter a dark room with two doors. Do you go through door #1 or door #2? > 2 You stare into the endless abyss at Cthuhlu's retina. 1. Blueberries. 2. Yellow jacket clothespins. 3. Understanding revolvers yelling melodies. > 3 The insanity rots your eyes into a pool of muck. Good job! ~/dev/ruby $ ruby ex31.rb
You enter a dark room with two doors. Do you go through door #1 or door #2?
> stuff
You stumble around and fall on a knife and die. Good job!
~/dev/ruby $ ruby ex31.rb You enter a dark room with two doors. Do you go through door #1 or door #2? > 1 There's a giant bear here eating a cheese cake. What do you do? 1. Take the cake. 2. Scream at the bear. > apples Well, doing apples is probably better. Bear runs away. |
ex31.rb:
def prompt
print "> "
end
puts "You enter a dark room with two doors. Do you go through door #1 or door #2?"
prompt; door = gets.chomp
if door == "1"
puts "There's a giant bear here eating a cheese cake. What do you do?"
puts "1. Take the cake."
puts "2. Scream at the bear."
prompt; bear = gets.chomp
if bear == "1"
puts "The bear eats your face off. Good job!"
elsif bear == "2"
puts "The bear eats your legs off. Good job!"
else
puts "Well, doing #{bear} is probably better. Bear runs away."
end
elsif door == "2"
puts "You stare into the endless abyss at Cthuhlu's retina."
puts "1. Blueberries."
puts "2. Yellow jacket clothespins."
puts "3. Understanding revolvers yelling melodies."
prompt; insanity = gets.chomp
if insanity == "1" or insanity == "2"
puts "Your body survives powered by a mind of jello. Good job!"
else
puts "The insanity rots your eyes into a pool of muck. Good job!"
end
else
puts "You stumble around and fall on a knife and die. Good job!"
end
|
Again, nothing of the comfort of swimming-pool edges (braces, etc.) to hang onto. This is a scripting language.
| Ruby navigation | |
|---|---|
| if | if expression true-part end |
| if | if expression true-part else false-part end |
| if | if expression true-part elseif expression true-part end |
| for | for variable in collection ... end |
| while | while expression ... end |
There are some cool things about arrays in Ruby. Check out Array for methods. There is no lack of functionality.
|
~/dev/ruby $ ruby ex32.rb This is count 1 This is count 2 This is count 3 This is count 4 This is count 5 A fruit of type: apples A fruit of type: oranges A fruit of type: pears A fruit of type: apricots I got 1 I got pennies I got 2 I got dimes I got 3 I got quarters Adding 0 to the list. Adding 1 to the list. Adding 2 to the list. Adding 3 to the list. Adding 4 to the list. Adding 5 to the list. Element was: 0 Element was: 1 Element was: 2 Element was: 3 Element was: 4 Element was: 5 |
ex32.rb:
the_count = [1, 2, 3, 4, 5]
fruits = ['apples', 'oranges', 'pears', 'apricots']
change = [1, 'pennies', 2, 'dimes', 3, 'quarters']
# this first kind of for-loop goes through an array
for number in the_count
puts "This is count #{number}"
end
# same as above, but using a block instead
fruits.each do |fruit|
puts "A fruit of type: #{fruit}"
end
# also we can go through mixed arrays too
for i in change
puts "I got #{i}"
end
# we can also build arrays, first start with an empty one
elements = []
# then use a range object to do 0 to 5 counts
for i in (0..5)
puts "Adding #{i} to the list."
# push is a function that arrays understand
elements.push(i)
end
# now we can puts them out too
for i in elements
puts "Element was: #{i}"
end
|
See syntax in previous exercise's comments.
|
~/dev/ruby $ ruby ex33.rb At the top i is 0 Numbers now: [0] At the bottom i is 1 At the top i is 1 Numbers now: [0, 1] At the bottom i is 2 At the top i is 2 Numbers now: [0, 1, 2] At the bottom i is 3 At the top i is 3 Numbers now: [0, 1, 2, 3] At the bottom i is 4 At the top i is 4 Numbers now: [0, 1, 2, 3, 4] At the bottom i is 5 At the top i is 5 Numbers now: [0, 1, 2, 3, 4, 5] At the bottom i is 6 The numbers: 0 1 2 3 4 5 |
ex33.rb:
i = 0
numbers = []
while i < 6
puts "At the top i is #{i}"
numbers.push(i)
i = i + 1
puts "Numbers now: #{numbers}"
puts "At the bottom i is #{i}"
end
puts "The numbers: "
for num in numbers
puts num
end
|
Arrays can be of most anything. They're defined using square brackets as
delimiters. They're subscripted as in other languages, e.g.:
array[ 0 ], but I don't show an exercise with that.
animals = ['bear', 'tiger', 'penguin', 'zebra'] colors = [ "red", "yellow", "blue", "green" ] digits = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] hexdigits = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ] hexnumbers = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f ]
I made up my own exercise just to validate what I posited in the previous paragraph. The tutorial had none.
|
~/dev/ruby $ ruby ex34.rb bear tiger penguin zebra red yellow blue green 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
ex34.rb: animals = ['bear', 'tiger', 'penguin', 'zebra'] colors = [ "red", "yellow", "blue", "green" ] digits = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] hexdigits = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ] hexnumbers = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f ] puts animals puts colors puts digits puts hexdigits puts hexnumbers |
There are some emerging standard practices in this script code such as how
to get input, e.g.: ans = gets.chomp.
Also, we're introduced to another concept, Ruby's array include? method that returns true/false that some value is an element of (in) the array. Here's some playing around to illustrate that:
~/dev/ruby $ irb irb(main):001:0> array = [ 'cat', 'dog', 'ant' ] => ["cat", "dog", "ant"] irb(main):002:0> array.include? "dog" => true irb(main):003:0> array = [ 1, 2, 3 ] => [1, 2, 3] irb(main):004:0> array.include?( 3 ) => true irb(main):005:0> array.include? 2 => true
Last, Ruby method to_i returns the leading characters of a string as an integer. With no base (in parens), decimal is assumed, otherwise a base in the range { 2..36 } can be specified.
|
~/dev/ruby $ ruby ex35.rb You are in a dark room. There is a door to your right and left. Which one do you take? > left There is a bear here. The bear has a bunch of honey. The fat bear is in front of another door. How are you going to move the bear? > taunt bear The bear has moved from the door. You can go through it now. > open door This room is full of gold. How much do you take? > asf Man, learn to type a number. Good job! |
ex35.rb:
def prompt()
print "> "
end
def gold_room()
puts "This room is full of gold. How much do you take?"
prompt; next_move = gets.chomp
if next_move.include? "0" or next_move.include? "1"
how_much = next_move.to_i()
else
dead("Man, learn to type a number.")
end
if how_much < 50
puts "Nice, you're not greedy, you win!"
Process.exit(0)
else
dead("You greedy bastard!")
end
end
def bear_room()
puts "There is a bear here."
puts "The bear has a bunch of honey."
puts "The fat bear is in front of another door."
puts "How are you going to move the bear?"
bear_moved = false
while true
prompt; next_move = gets.chomp
if next_move == "take honey"
dead("The bear looks at you then slaps your face off.")
elsif next_move == "taunt bear" and not bear_moved
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
elsif next_move == "taunt bear" and bear_moved
dead("The bear gets pissed off and chews your leg off.")
elsif next_move == "open door" and bear_moved
gold_room()
else
puts "I got no idea what that means."
end
end
end
def cthulhu_room()
puts "Here you see the great evil Cthulhu."
puts "He, it, whatever stares at you and you go insane."
puts "Do you flee for your life or eat your head?"
prompt; next_move = gets.chomp
if next_move.include? "flee"
start()
elsif next_move.include? "head"
dead("Well that was tasty!")
else
cthulhu_room()
end
end
def dead(why)
puts "#{why} Good job!"
Process.exit(0)
end
def start()
puts "You are in a dark room."
puts "There is a door to your right and left."
puts "Which one do you take?"
prompt; next_move = gets.chomp
if next_move == "left"
bear_room()
elsif next_move == "right"
cthulhu_room()
else
dead("You stumble around the room until you starve.")
end
end
start()
|
The author gives some best-practice advice. Some are interesting, some are not and some may not appear interesting now, but may become so with more experience. Some of this advice smells like the absence of TDD. Maybe Ruby can't do that.
Again, no code exercise, but a list of keywords, datatypes, escape sequences and operators to review.
The objective here is to go through the code analyzing an imaginary Ruby translation of it to understand what Ruby's doing for:
mystuff.push( "hello " )
The steps are given in the exercise, but they're sort of a reduced human view. These are (in my own words which are very different from the authors):
One is expected to undertake such a description for every identifier in the exercise code below.
|
~/dev/ruby $ ruby ex38.rb Wait there's not 10 things in that list, let's fix that. Adding: Boy There's 7 items now. Adding: Girl There's 8 items now. Adding: Banana There's 9 items now. Adding: Corn There's 10 items now. There we go: ["Apples", "Oranges", "Crows", "Telephone", "Light", "Sugar", "Boy", "Girl", "Banana", "Corn"] Let's do some things with stuff. Oranges Corn Corn Apples Oranges Crows Telephone Light Sugar Boy Girl Banana Telephone#Sugar |
ex38.rb:
ten_things = "Apples Oranges Crows Telephone Light Sugar"
puts "Wait there's not 10 things in that list, let's fix that."
stuff = ten_things.split(' ')
more_stuff = %w(Day Night Song Frisbee Corn Banana Girl Boy)
while stuff.length != 10
next_one = more_stuff.pop()
puts "Adding: #{next_one}"
stuff.push(next_one)
puts "There's #{stuff.length} items now."
end
puts "There we go: #{stuff}"
puts "Let's do some things with stuff."
puts stuff[1]
puts stuff[-1] # whoa! fancy
puts stuff.pop()
puts stuff.join(' ') # what? cool!
puts stuff.values_at(3,5).join('#') # super stellar!
|
In Ruby, a hash is what is more properly termed a map or, if the notion of the underlying indexing strategy is to be included, a hash map. The tutorial author's explanation is somewhat torturous, but it has to be remembered that he's targeting essentially a non-programming audience.
Hashmaps do not have order. If you print one out, it comes out in whatever order they're stored in, which from my observation is, at least at first, the order in which they're built.
This construct is what imparts the greatest amount of obfuscation to reading Ruby when coming from another language. It's the "hash rocket" that does it.
And, suddenly, the use of braces springs into Ruby life for the purpose of hashmap initialization.
|
~/dev/ruby $ ruby ex39.rb ---------- NY State has: New York OR State has: Portland ---------- Michigan's abbreviation is: MI Florida's abbreviation is: FL ---------- Michigan has: Detroit Florida has: Jacksonville ---------- Oregon is abbreviated OR Florida is abbreviated FL California is abbreviated CA New York is abbreviated NY Michigan is abbreviated MI ---------- CA has the city San Francisco MI has the city Detroit FL has the city Jacksonville NY has the city New York OR has the city Portland ---------- Oregon state is abbreviated OR and has city Portland Florida state is abbreviated FL and has city Jacksonville California state is abbreviated CA and has city San Francisco New York state is abbreviated NY and has city New York Michigan state is abbreviated MI and has city Detroit ---------- Sorry, no Texas. The city for the state 'TX' is: Does Not Exist |
ex39.rb:
# create a mapping of state to abbreviation
states = {
'Oregon' => 'OR',
'Florida' => 'FL',
'California' => 'CA',
'New York' => 'NY',
'Michigan' => 'MI'
}
# create a basic set of states and some cities in them
cities = {
'CA' => 'San Francisco',
'MI' => 'Detroit',
'FL' => 'Jacksonville'
}
# add some more cities
cities['NY'] = 'New York'
cities['OR'] = 'Portland'
# puts out some cities
puts '-' * 10
puts "NY State has: ", cities['NY']
puts "OR State has: ", cities['OR']
# puts some states
puts '-' * 10
puts "Michigan's abbreviation is: ", states['Michigan']
puts "Florida's abbreviation is: ", states['Florida']
# do it by using the state then cities dict
puts '-' * 10
puts "Michigan has: ", cities[states['Michigan']]
puts "Florida has: ", cities[states['Florida']]
# puts every state abbreviation
puts '-' * 10
for state, abbrev in states
puts "%s is abbreviated %s" % [state, abbrev]
end
# puts every city in state
puts '-' * 10
for abbrev, city in cities
puts "%s has the city %s" % [abbrev, city]
end
# now do both at the same time
puts '-' * 10
for state, abbrev in states
puts "%s state is abbreviated %s and has city %s" % [
state, abbrev, cities[abbrev]]
end
puts '-' * 10
# if it's not there you get nil
state = states['Texas']
if not state
puts "Sorry, no Texas."
end
# get a city with a default value
city = cities['TX'] || 'Does Not Exist'
puts "The city for the state 'TX' is: %s" % city
|
Ruby lays claim to object-orientation and so sports the ability to define classes. The author thinks there's something peculiarly unintuitive about object-oriented programming and tippy-toes around calling it "just plain weird." He compares Ruby modules to hashmaps and classes to modules
The tutorial text does contain examples slightly more interesting than the exercise code here. It's best to examine the examples while ignoring the comments about how hard OOP is.
|
~/dev/ruby $ ruby ex40.rb Happy birthday to you I don't want to get sued So I'll stop right there They rally around the family With pockets full of shells |
ex40.rb:
class Song
def initialize(lyrics)
@lyrics = lyrics
end
def sing_me_a_song()
for line in @lyrics
puts line
end
end
end
happy_bday = Song.new(["Happy birthday to you",
"I don't want to get sued",
"So I'll stop right there"])
bulls_on_parade = Song.new(["They rally around the family",
"With pockets full of shells"])
happy_bday.sing_me_a_song()
bulls_on_parade.sing_me_a_song()
|
Just as a couple of exercises ago when trying to explain how Ruby scans, parses and executed code, the author has a rather strange way of looking at this stuff.
|
~/dev/ruby $ ruby ex41.rb class Boot def initialize(cup) end end > class Boot has-a initialize that takes cup as a parameter. ANSWER: class Boot has-a initialize that takes cup parameters. border = Cap.new() > border gets a new Cap. ANSWER: Set border to an instance of class Cap. cord.appliance = 'account' > Set cord's appliance to the string "account". ANSWER: From cord get the appliance attribute and set it to 'account'. chicken.doctor(bridge, beef) > Call chicken's doctor passing bridge and beef as parameters. ANSWER: From chicken get the doctor function, and call it with parameters bridge, beef. class Arch < Beetle end > Class Arch inherits class Beetle's attributes. ANSWER: Make a class named Arch that is-a Beetle. class Cry def celery(drawer) end end ^D ~/dev/ruby $ ruby ex41.rb english From church get the cart attribute and set it to 'cord'. > church.cart = 'cord' ANSWER: church.cart = 'cord' class Bubble has-a function named crush that takes drug parameters. > class Bubble def crush( drug ) end end ANSWER: class Bubble def crush(drug) end end Set channel to an instance of class Advertisement. > channel = Advertisement.new() ANSWER: channel = Advertisement.new() From door get the arithmetic function, and call it with parameters curtain, basketball. > door.arithmetic( curtain, basketball ) ANSWER: door.arithmetic(curtain, basketball) Make a class named Cactus that is-a Aftermath. ^D |
ex41.rb:
require 'open-uri'
WORD_URL = "http://learncodethehardway.org/words.txt"
WORDS = []
PHRASES = {
"class ### < ###\nend" => "Make a class named ### that is-a ###.",
"class ###\n\tdef initialize(@@@)\n\tend\nend" => "class ### has-a initialize that takes @@@ parameters.",
"class ###\n\tdef ***(@@@)\n\tend\nend" => "class ### has-a function named *** that takes @@@ parameters.",
"*** = ###.new()" => "Set *** to an instance of class ###.",
"***.***(@@@)" => "From *** get the *** function, and call it with parameters @@@.",
"***.*** = '***'" => "From *** get the *** attribute and set it to '***'."
}
PHRASE_FIRST = ARGV[0] == "english"
open(WORD_URL) {|f|
f.each_line {|word| WORDS.push(word.chomp)}
}
def craft_names(rand_words, snippet, pattern, caps=false)
names = snippet.scan(pattern).map do
word = rand_words.pop()
caps ? word.capitalize : word
end
return names * 2
end
def craft_params(rand_words, snippet, pattern)
names = (0...snippet.scan(pattern).length).map do
param_count = rand(3) + 1
params = (0...param_count).map {|x| rand_words.pop() }
params.join(', ')
end
return names * 2
end
def convert(snippet, phrase)
rand_words = WORDS.sort_by {rand}
class_names = craft_names(rand_words, snippet, /###/, caps=true)
other_names = craft_names(rand_words, snippet, /\*\*\*/)
param_names = craft_params(rand_words, snippet, /@@@/)
results = []
for sentence in [snippet, phrase]
# fake class names, also copies sentence
result = sentence.gsub(/###/) {|x| class_names.pop }
# fake other names
result.gsub!(/\*\*\*/) {|x| other_names.pop }
# fake parameter lists
result.gsub!(/@@@/) {|x| param_names.pop }
results.push(result)
end
return results
end
# keep going until they hit CTRL-D
loop do
snippets = PHRASES.keys().sort_by {rand}
for snippet in snippets
phrase = PHRASES[snippet]
question, answer = convert(snippet, phrase)
if PHRASE_FIRST
question, answer = answer, question
end
print question, "\n\n> "
exit(0) unless STDIN.gets
puts "\nANSWER: %s\n\n" % answer
end
end
|
There is no coding exercise, this is all about the distinction between objects and classes, "is-a" and "has-a".
|
~/dev/ruby $ ruby ex42.rb |
ex42.rb:
## Animal is-a object look at the extra credit
class Animal
end
## ??
class Dog < Animal
def initialize(name)
## ??
@name = name
end
end
## ??
class Cat < Animal
def initialize(name)
## ??
@name = name
end
end
## ??
class Person
def initialize(name)
## ??
@name = name
## Person has-a pet of some kind
@pet = nil
end
attr_accessor :pet
end
## ??
class Employee < Person
def initialize(name, salary)
## ?? hmm what is this strange magic?
super(name)
## ??
@salary = salary
end
end
## ??
class Fish
end
## ??
class Salmon < Fish
end
## ??
class Halibut < Fish
end
## rover is-a Dog
rover = Dog.new("Rover")
## ??
satan = Cat.new("Satan")
## ??
mary = Person.new("Mary")
## ??
mary.pet = satan
## ??
frank = Employee.new("Frank", 120000)
## ??
frank.pet = rover
## ??
flipper = Fish.new()
## ??
crouse = Salmon.new()
## ??
harry = Halibut.new()
|
This is a lot of code that's a game. I'm not going to put here. You're just supposed to trace through it to see how it works.
Another volley in the "I don't get OOP series", the author clearly does and takes pains to explain the distinction in Ruby. What's to retain from all of this? The Ruby syntax, of course.
See the use of super().
|
~/dev/ruby $ ruby ex44-1.rb PARENT implicit() PARENT implicit() |
ex44-1.rb:
class Parent
def implicit()
puts "PARENT implicit()"
end
end
class Child < Parent
end
dad = Parent.new()
son = Child.new()
dad.implicit()
son.implicit()
|
|
|
~/dev/ruby $ ruby ex44-2.rb PARENT override() CHILD override() |
ex44-2.rb:
class Parent
def override()
puts "PARENT override()"
end
end
class Child < Parent
def override()
puts "CHILD override()"
end
end
dad = Parent.new()
son = Child.new()
dad.override()
son.override()
|
|
|
~/dev/ruby $ ruby ex44-3.rb PARENT altered() CHILD, BEFORE PARENT altered() PARENT altered() CHILD, AFTER PARENT altered() |
ex44-3.rb:
class Parent
def altered()
puts "PARENT altered()"
end
end
class Child < Parent
def altered()
puts "CHILD, BEFORE PARENT altered()"
super()
puts "CHILD, AFTER PARENT altered()"
end
end
dad = Parent.new()
son = Child.new()
dad.altered()
son.altered()
|
|
|
~/dev/ruby $ ruby ex44-4.rb PARENT implicit() PARENT implicit() PARENT override() CHILD override() PARENT altered() CHILD, BEFORE PARENT altered() PARENT altered() CHILD, AFTER PARENT altered() |
ex44-4.rb:
class Parent
def override()
puts "PARENT override()"
end
def implicit()
puts "PARENT implicit()"
end
def altered()
puts "PARENT altered()"
end
end
class Child < Parent
def override()
puts "CHILD override()"
end
def altered()
puts "CHILD, BEFORE PARENT altered()"
super()
puts "CHILD, AFTER PARENT altered()"
end
end
dad = Parent.new()
son = Child.new()
dad.implicit()
son.implicit()
dad.override()
son.override()
dad.altered()
son.altered()
|
|
|
~/dev/ruby $ ruby ex44-5.rb OTHER implicit() CHILD override() CHILD, BEFORE OTHER altered() OTHER altered() CHILD, AFTER OTHER altered() |
ex44-5.rb:
class Other
def override()
puts "OTHER override()"
end
def implicit()
puts "OTHER implicit()"
end
def altered()
puts "OTHER altered()"
end
end
class Child
def initialize()
@other = Other.new()
end
def implicit()
@other.implicit()
end
def override()
puts "CHILD override()"
end
def altered()
puts "CHILD, BEFORE OTHER altered()"
@other.altered()
puts "CHILD, AFTER OTHER altered()"
end
end
son = Child.new()
son.implicit()
son.override()
son.altered()
|
|
|
~/dev/ruby $ ruby ex44-6.rb OTHER implicit() CHILD override() CHILD, BEFORE OTHER altered() OTHER altered() CHILD, AFTER OTHER altered() |
ex44-6.rb:
module Other
def Other.override()
puts "OTHER override()"
end
def Other.implicit()
puts "OTHER implicit()"
end
def Other.altered()
puts "OTHER altered()"
end
end
class Child
def implicit()
Other.implicit()
end
def override()
puts "CHILD override()"
end
def altered()
puts "CHILD, BEFORE OTHER altered()"
Other.altered()
puts "CHILD, AFTER OTHER altered()"
end
end
son = Child.new()
son.implicit()
son.override()
son.altered()
|
Coding assignment is to write a game. Good luck with that if you've nothing better to do. See Exercise 45: You Make a Game.
This exercise is interested in that it reveals the mind of one Ruby programmer's way of setting up Ruby projects. Not all of this is clear. For instance, the author uses the expression, project's root directory without defining it. Which of the following is that directory?
~/dev/ruby $ tree projects projects +-- skeleton + bin + lib | + NAME | | ` version.rb | ` NAME.rb + NAME.gemspec ` test ` test_NAME.rb 5 directories, 4 files
Note that NAME everywhere is to be replaced by the name of your project.
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "NAME/version"
Gem::Specification.new do |s|
s.name = "NAME"
s.version = NAME::VERSION
s.authors = ["Rob Sobers"]
s.email = ["[email protected]"]
s.homepage = ""
s.summary = %q{TODO: Write a gem summary}
s.description = %q{TODO: Write a gem description}
s.rubyforge_project = "NAME"
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end
In the previous file, we see the path "../lib" which encourages us not to see skeleton as the project root, but we'll have to see.
Here are the steps according to the author to use this skeleton.
Assuming your new project is named, "accountmgr", you should see something like this afterward:
~/dev/ruby $ tree projects projects +-- accountmgr + bin + lib | + accountmgr | | ` version.rb | ` accountmgr.rb + accountmgr.gemspec ` test ` test_accountmgr.rb 5 directories, 4 files
...and...
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "accountmgr/version"
Gem::Specification.new do |s|
s.name = "accountmgr"
s.version = accountmgr::VERSION
s.authors = ["Rob Sobers"]
s.email = ["[email protected]"]
s.homepage = ""
s.summary = %q{TODO: Write a gem summary}
s.description = %q{TODO: Write a gem description}
s.rubyforge_project = "accountmgr"
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end
A really good thing to do now is to read up on the gemspec file. For one thing, it appears that you should create a script in the bin subdirectory that's referenced from the gemspec file. This is not yet shown here. Here are some useful links to study.
Ah, now we're getting down to business: writing a test case...
See the structure under ruby/projects/ex47 to observe this exercise.
|
~/dev/ruby $ cd ruby/projects/ex47/test ; ruby test_ex47.rb Run options: # Running tests: ... Finished tests in 0.000732s, 4100.5566 tests/s, 9567.9654 assertions/s. 3 tests, 7 assertions, 0 failures, 0 errors, 0 skips |
ruby/projects/ex47/lib/ex47.rb:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
@name = name
@description = description
@paths = {}
end
def go(direction)
@paths[direction]
end
def add_paths(paths)
@paths.update(paths)
end
end
ruby/projects/ex47/test/test_ex47.rb:
require 'test/unit'
require_relative '../lib/ex47'
class MyUnitTests < Test::Unit::TestCase
def test_room()
gold = Room.new("GoldRoom",
"""This room has gold in it you can grab. There's a
door to the north.""")
assert_equal(gold.name, "GoldRoom")
assert_equal(gold.paths, {})
end
def test_room_paths()
center = Room.new("Center", "Test room in the center.")
north = Room.new("North", "Test room in the north.")
south = Room.new("South", "Test room in the south.")
center.add_paths({:north => north, :south => south})
assert_equal(center.go(:north), north)
assert_equal(center.go(:south), south)
end
def test_map()
start = Room.new("Start", "You can go west and down a hole.")
west = Room.new("Trees", "There are trees here, you can go east.")
down = Room.new("Dungeon", "It's dark down here, you can go up.")
start.add_paths({:west => west, :down => down})
west.add_paths({:east => start})
down.add_paths({:up => start})
assert_equal(start.go(:west), west)
assert_equal(start.go(:west).go(:east), start)
assert_equal(start.go(:down).go(:up), start)
end
end
|
Best practices for Ruby testing...
Hoorah! We get to write a test again!
The trick of this exercise is to force you to create the Lexicon code from the given test code, a sort of brutal TDD exercise.
As (always!) compared to Java, ...
...in irb we can generate sample errors:
irb(main):005:0> Integer( 3 ) => 3 irb(main):006:0> Integer( 3.2 ) => 3 irb(main):007:0> Integer( cat ) NameError: undefined local variable or method `cat' for main:Object from (irb):7 from /usr/bin/irb:12:in `<main>' irb(main):008:0> Integer( "cat" ) ArgumentError: invalid value for Integer(): "cat" from (irb):8:in `Integer' from (irb):8 from /usr/bin/irb:12:in `<main>'
These errors are caught via the rescue mechanism in Ruby:
def convert_number( number )
begin
Integer( number )
rescue ArgumentError
puts "ArgumentError--not a number"
nil
end
end
So, begin ... rescue ... end is like try { ... } catch { ... }.
|
~/dev/ruby/projects/lexicon/test $ ruby test_lexicon.rb ruby test_lexicon.rb Run options: # Running tests: north north south east .ASDFADFASDF bear IAS princess .bear bear princess .1234 3 91234 .the the in of .go go kill eat . Finished tests in 0.000892s, 6725.6506 tests/s, 13451.3012 assertions/s. 6 tests, 12 assertions, 0 failures, 0 errors, 0 skips |
projects/lexicon/lib/lexicon.rb:
class Lexicon
attr_accessor :lexicon, :dict
def initialize()
@dict = {
:north => :direction,
:south => :direction,
:east => :direction,
:west => :direction,
:go => :verb,
:kill => :verb,
:eat => :verb,
:bear => :noun,
:princess => :noun,
:the => :stop,
:in => :stop,
:of => :stop,
}
end
Pair = Struct.new(:qualifier, :value)
def scan(list)
pairs = []
list.split.map do | value |
puts value
if value.match( /^[0-9]+$/ )
pairs.push( Pair.new(:number, value.to_i) )
elsif qualifier = @dict[value.intern]
pairs.push( Pair.new(qualifier, value) )
else
pairs.push( Pair.new(:error, value.to_s) )
end
end
pairs
end
end
projects/lexicon/test/test_lexicon.rb:
require 'test/unit'
require_relative "../lib/lexicon/lexicon"
class LexiconTests < Test::Unit::TestCase
Pair = Lexicon::Pair
@@lexicon = Lexicon.new()
def test_directions()
assert_equal([Pair.new(:direction, 'north')], @@lexicon.scan("north"))
result = @@lexicon.scan("north south east")
assert_equal(result, [Pair.new(:direction, 'north'),
Pair.new(:direction, 'south'),
Pair.new(:direction, 'east')])
end
def test_verbs()
assert_equal(@@lexicon.scan("go"), [Pair.new(:verb, 'go')])
result = @@lexicon.scan("go kill eat")
assert_equal(result, [Pair.new(:verb, 'go'),
Pair.new(:verb, 'kill'),
Pair.new(:verb, 'eat')])
end
def test_stops()
assert_equal(@@lexicon.scan("the"), [Pair.new(:stop, 'the')])
result = @@lexicon.scan("the in of")
assert_equal(result, [Pair.new(:stop, 'the'),
Pair.new(:stop, 'in'),
Pair.new(:stop, 'of')])
end
def test_nouns()
assert_equal(@@lexicon.scan("bear"), [Pair.new(:noun, 'bear')])
result = @@lexicon.scan("bear princess")
assert_equal(result, [Pair.new(:noun, 'bear'),
Pair.new(:noun, 'princess')])
end
def test_numbers()
assert_equal(@@lexicon.scan("1234"), [Pair.new(:number, 1234)])
result = @@lexicon.scan("3 91234")
assert_equal(result, [Pair.new(:number, 3),
Pair.new(:number, 91234)])
end
def test_errors()
assert_equal(@@lexicon.scan("ASDFADFASDF"), [Pair.new(:error, 'ASDFADFASDF')])
result = @@lexicon.scan("bear IAS princess")
assert_equal(result, [Pair.new(:noun, 'bear'),
Pair.new(:error, 'IAS'),
Pair.new(:noun, 'princess')])
end
end
|
|
parsererror.rb:
class ParserError < Exception
end
class Sentence
def initialize(subject, verb, object)
# remember we take Pair.new(:noun, "princess") structs and convert them
@subject = subject.word
@verb = verb.word
@object = object.word
end
end
module Parser
def self.peek(word_list)
begin
word_list.first.token
rescue
nil
end
end
def self.match(word_list, expecting)
begin
word = word_list.shift
if word.token == expecting
word
else
nil
end
rescue
nil
end
end
def self.skip(word_list, token)
while peek(word_list) == token
match(word_list, token)
end
end
def self.parse_verb(word_list)
skip(word_list, :stop)
if peek(word_list) == :verb
return match(word_list, :verb)
else
raise ParserError.new("Expected a verb next.")
end
end
def self.parse_object(word_list)
skip(word_list, :stop)
next_word = peek(word_list)
if next_word == :noun
return match(word_list, :noun)
end
if next_word == :direction
return match(word_list, :direction)
else
raise ParserError.new("Expected a noun or direction next.")
end
end
def self.parse_subject(word_list, subj)
verb = parse_verb(word_list)
obj = parse_object(word_list)
return Sentence.new(subj, verb, obj)
end
def self.parse_sentence(word_list)
skip(word_list, :stop)
start = peek(word_list)
if start == :noun
subj = match(word_list, :noun)
return parse_subject(word_list, subj)
elsif start == :verb
# assume the subject is the player then
return parse_subject(word_list, Pair.new(:noun, "player"))
else
raise ParserError.new("Must start with subject, object, or verb not: #{start}")
end
end
end
|
...were begun last lesson. Here's how to generate them; compare with statements in the code above:
raise ParserError.new( "Must start with subject, object or verb." )
The code above is an illustration of using an existing module named Parser, which is written here as the bottom majority of this code. It's a way to package up methods so that they don't conflict with anything else in Ruby or your Ruby code. In essence, the Ruby module wraps all the methods with a name. You consume module methods merely by stating the module name, adding the dot operator and then the name of the method you want to use.
Parser.parse_verb( word_list )
At this point, I've bogged down too much and am out of time. My only purpose was to learn Ruby syntax and constructs. I found learning Ruby practices like the organization of a Ruby project fascinating, but I need to get back to Chef and using Ruby for that. The remainder of these exercises is left for another time.