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.