Ruby Cookbook by Lucas Carlson, Leonard Richardson The unconfirmed error reports are from readers. They have not yet been approved or disproved by the author or editor and represent solely the opinion of the reader. Here's a key to the markup: [page-number]: serious technical mistake {page-number}: minor technical mistake : important language/formatting problem (page-number): language change or minor formatting problem ?page-number?: reader question or request for clarification This page was updated February 11, 2008. UNCONFIRMED errors and comments from readers: {10} line immediately above "Discussion"; There should be a space between "wrong" and the period that follows it s.split(/\b/).reverse!.join(' ') # => "These words are in the wrong . order" (18) 2nd code example under Discussion heading; For the example describing a pattern match for anything that isn't whitespace. It should be a lowercase 's'. So the example should read: /[^\s]+/ The capital 'S' which the book uses results in only whitespace since you're negating the 'S' match. A simpler alternative would be: /\S+/ {25} first paragraph under heading See Also; Paragraph refers to chapter 11.2 for details on the use of the iconv library. Nevertheless iconv is not mentioned in chapter 11.2. {53} end of Solution; In the book, the formula to convert a logarithm from base b1 to base b2 looks like this: log_b1(x) = log_b2(x) / log_b2(k) however, the 'k' should be 'b1' as in log_b1(x) = log_b2(x) / log_b2(b1) See also http://en.wikipedia.org/wiki/Logarithm#Change_of_base (53) Discussion, line 2-4; The sentence reads: "That is, Math.log10(1000)==3.0 because 10 cubed is 1000.Math.log(Math::E)==1 because e to the first power is e." A space between '1000.' and 'Math.log(Math::E)' is missing - the above are actually two sentences. (55) 2.8 first code example; (Note: Since I'm reading the book via the Safari bookshelf, I have no idea which page it is; but it is anyway chapater 2.8, titled "Recipe 2.8. Finding Mean, Median, and Mode" The code example def mean(array) array.inject(array.inject(0) { |sum, x| sum += x } / array.size.to_f end has two problems, one a typo (which you likely have already noticed), and a more subtle pedagogic one: The correct way to write the function would be IMO def mean(array) array.inject(0) { |sum,x| sum+x } / array.size.to_f end Apart from the obvious correction of the typo at the beginning of the statement, note that I changed sum+=x to sum+x. While both expressions would work here, sum+=x silently implies that the incject function *should* modify the running variable (here: sum). This is not true. Changing sum within the block has no effect outside. Array#incject only takes the return value of the block. In this case, sum+x and sum+=x yield the same return value, but sum+x communicates this fact in a clearer way. [91] class Date def Date.now...; class Date def Date.now return Date.jd(Datetime.now.jd) end end puts Date.now does not work. It generates the following error message: `now': stack level too deep (SystemStackError) Remedy: change to class Date def Date.new Date.jd(DateTime.now.jd) end end puts Date.new [111] Bottom code fragment; to_local_time is incorrect, and will return a date several years off. Original: def to_local_time to_time(new_offset(DateTime.now.offset-offset), :local) end Counter-example: Consider the call DateTime.now.to_local_time, for any time zone, such as PST. This results in to_time(new_offset(0), :local) which invokes Time.local with offset(0) H:M:S. This should be: def to_local_time to_time(new_offset(DateTime.now.offset), :local) end {112} code at bottom of page in normalize_time_types(array); On the next to last line on the page, in the line of code that's part of the rescue statement convert_to = DateTime is superfluous. The variable convert_to is never referenced. {138} code related to the else clauses in the revised class []= for SortedArray; The following code in the two "else" clauses that contain the comment "# Not supported. Delegate to superclass; will probably give an error." is incorrect: else # Not supported... super sort!(&sort_by) end else # Not supported... super sort!(&sort_by) end should be: else # Not supported... super sort!(&@sort_by) end else # Not supported... super sort!(&@sort_by) end This is no big deal since the code should never execute. {139} line 6 from bottom of page 139; stripes = SortedArray.new(["aardwolf", "zebrafish"]) should be stripes = FrozenCopySortedArray.new(["aardwolf", "zebrafish"]) then if you ran stripes[1].upcase! you will get the following error message: `upcase!': can't modify frozen string (TypeError) (141) 1st paragraph under "Discussion"; The author refers to a variable, total, in the example above. However, the example uses the variable sum. Also, the first sentence of the paragraph reads: "...we didn't need to define the variable total variable outside the scope of iteration." One of the instances of the word variable needs to be removed, and the sentence should be adjusted, depending on which instance is removed. [146] line 5 and 6 from bottom of page 146; The SortedList class from Recipe 4.7 is used for this task. The min_n method below create a SortedList "stable" that ..... The "SortedList" of lines 5 and 6 should be "SortedArray". If you look at Recipe 4.7 and source code of page 147, there is a "SortedArray" class instead of "SortedList" class. (148) Comment line describing result after running hash.update (8th line from bottom of page); # => {5=>"five", 1=>"ontwo", 2=>"two", 3=>"three", 4=>"four"} should be # => {5=>"five", 1=>"one", 2=>"two", 3=>"three", 4=>"four"} NOTE: "ontwo" after 1=> is a typo (151) middle of the pages; "then use use Array#compress! to remove them." has too many uses. {151} last paragraph (before code); There's no method called Array#compress!. The correct method for removing nil values is Array#compact! [151] code at bottom of page defining strip_values_at!; There's a misplaced "end" The code in the book is: class Array def strip_values_at!(*args) # For each mentioned index, replace its value with a dummy object values = [] dummy = Object.new args.each do | i | if i < size values << self[i] self[i] = dummy end #Strip out the dummy objects delete(dummy) return values end end end Correct code should be: class Array def strip_values_at!(*args) # For each mentioned index, replace its value with a dummy object values = [] dummy = Object.new args.each do | i | if i < size values << self[i] self[i] = dummy end end # Strip out the dummy objects delete(dummy) return values end end {153} 3rd set of code (from either the top or the bottom of the page); The following code is incorrect: array = [1, 2, 3] set = [3, 4, 5].to_s array & set set & array It should be the following: array = [1, 2, 3] set = [3, 4, 5].to_set array & set set & array NOTE: to_s should be to_set (155) 4th to last line of code on the page (a comment); The comment is missing a closing quotation mark for "String" The text should be: # Divide the set into the "String" subset, the "Fixnum" subset, and # the "Float" subset {156} 2nd section of code above Discussion; The result for the operation to divide the set into sets of adjacent numbers is wrong. There was no element "-3" in Set s. There was a "-2". The incorrect results in the book are: #, #, #}> The correct results should be: #, #, #}> {156} section of code immediate preceding Discussion; The following assignment statement should precede the code that uses Set#classify; s = Set.new([1, 2, 3, 'a', 'b', 'c', -1.0, -2.0, -3.0]) (160) Last code example on page; The following has an error (missing colons in key labels): h = { :one_squared => 1, two_squared => 4, three_squared => 9, :four_squared => 16 } The corrected code should be: h = { :one_squared => 1, :two_squared => 4, :three_squared => 9, :four_squared => 16 } (165) 2nd section of code up from the bottom of the page; The example in the book is: squares = [1, 1, 2, 3, 4, 9] results = {} squares.into_hash(results) # => {1=>1, 2=>3, 4=>9} It would seem that this is a typo and that the correct version should be: squares = [1, 1, 2, 4, 3, 9] results = {} squares.into_hash(results) # => {1=>1, 2=>4, 3=>9} (165) Last section of code on page; The values used for the hash named cubes causes confusion. cubes = { 3 => 27, 4 => 256, 5 => 3125 } I recommend using one of the following instead: cubes = { 3 => 27, 4 => 64, 5 => 125 } cubes = { 3 => 3 ** 3, 4 => 4 ** 3, 5 => 5 ** 3 } (167) 1st section of code on page; There is no value shown for the 8th line of code: h.delete(5) The actual value returned is nil: h.delete(5) # => nil {199} Next to last line of code on page; d.reject { |f| f[0] == '.' } generates the following output: [".", "..", ".hidden_file", "ruby_script.rb", "subdirectory", "text_file"] The problem is that f[0] returns a Fixnum, not a string. Any of the following will work properly: d.reject { |f| f[0] == ?. } d.reject { |f| f[0..0] == '.' } d.reject { |f| f[/^\./] == '.' } d.reject { |f| f =~ [/^\./ } [206] The last two lines of code at top of page (just above "See Also"); First, IO.sync, as it appears in the code, is invoking a class method. IO#sync is an instance method. Second, in order for the output to go straight into the OS buffer, you'd want to set IO#sync = true (not false). Either of the following corrects what's in the book: file = open('output', 'w') file.sync = true file << 'This is going straight into the OS buffer.' file.close open('output', 'w') do | f | f.sync = true f << 'This is going straight into the OS buffer.' end {227} top line in the set of code at the bottom of the page; Throughout the examples in 6.17, you add "b" to the file mode which is needed when you open a binary file on Windows. In the first set line of code after Discussion, you omit this. Change f = open('binary') to f = open('binary', 'rb') [231] 1st line of code in 6.18; The first line of code looks like Java code instead of Ruby code The following line of code import 'fileutils' should be changed to require 'fileutils' [232] Last two sections of code on the page; The following code: open(filename, File::TRUNC) do | f | generates the following error in `initialize': Invalid argument - truncate.txt (Errno::EINVAL) The same open statement appears twice in the text. Note: The value of the constatn File::TRUNC is 512. {245} Code at bottom of page; If you want to pick numbers between min and max, inclusive, then you should change limit.times { yield min+rand(max+1) } to limit.times { yield min+rand(max) } The implementation defined in the book chooses numbers between 1 and 50 (rand(49) returns a value between 0 and 48) The code still has the problem that it can pick the same number more than once. [249] 1st sample code block; When the each method recurses with the line "child.each { |e| yield e }" it is wrapping the original block with multiple successive blocks as it recurses deeper into the tree. This seems very inefficient. This can easily be seen by putting a print statement into the block so that the printed output clearly shows this wrapping... child_node.each { |e| puts "yield #{e}"; yield e } I believe a better version of the each method would use a named block thusly... def each(&block) block.call(@value) @children.each do |child_node| child_node.each(&block) end end The evolution of this solution was very instructive for me and might be worth discussing in the next version of the cook book as a way of reinforcing how yield and blocks/closures work. [271] 3rd paragraph; The book says: "Calling attr_accessor :instance_variable generates both the getter method speaks_english and the setter method speaks_english=" I believe it should say: "Calling attr_accessor :speaks_english generates both the getter method speaks_english and the setter method speaks_english=" (279) Second paragraph under "Solution"; "...by passing the coordinates of its top-left and bottom-left corners..." should be "...by passing the coordinates of its top-left and bottom-right corners..." {281} 2nd paragraph after Solution; ArgumentException should be ArgumentError (or ArgumentError exception) (285) 2nd paragraph; The text refers to a class called CardinalNumber. The code defines a class called OrdinalNumber. The text should change CardinalNumber to OrdinalNumber (occurs 3 times). The comment at the beginning of the code states: # An integer represented as an ordinal number (1st, 2nd, 3rd...), as # opposed to an ordinal number (1, 2, 3...) It should be changed to: # An integer represented as an ordinal number (1st, 2nd, 3rd...), as # opposed to an cardinal number (1, 2, 3...) {285} code; There are two problems with the code: 1. The line below check = abs should be changed from: if to_check == 11 or to_check == 12 to if check == 11 or check == 12 or check == 13 2. There's a line missing from the case expression. Change from: case check % 10 when 1 then suffix = "st" when 2 then suffix = "nd" else suffix = "th" end to: case check % 10 when 1 then suffix = "st" when 2 then suffix = "nd" when 3 then suffix = "rd" else suffix = "th" end You might also want to add to the bottom of the code: OrdinalNumber.new(3).to_s # => "3rd" OrdinalNumber.new(13).to_s # => "13th" OrdinalNumber.new(123).to_s # => "123rd" {286} code in the center of the page; change a = AppendOnlyArray to a = AppendOnlyArray.new (287) Under See Also; Change CardinalNumber to OrdinalNumber {326} code above "See Also"; The last line of code before the final end is: RubyString.new("This is a built-in string, not a StringTheory2::String") This code should be moved below the end, and changed to: StringTheory2::RubyString.new("This is a built-in string, not a StringTheory2::String") The full example is: module StringTheory2 RubyString = String class String def initialize(length=1.0e-33) @length = length end end end StringTheory2::RubyString.new("This is a built-in string, not a StringTheory2::String") #=>"This is a built-in string, not a StringTheory2::String" (339) Text under Problem; There's a word missing. change "You want to the name of a method..." to "You want to put the name of a method..." {341} code at the top of the page; Although it doesn't change anything, I think the 5th line of code at the top of the page should be changed from: Welcomer.method("an_instance_method") to Welcomer.method("an_instance_method").call (351) code appearing inside the Problem section; The following code: class RGBColor(red=0, green=0, blue=0) @red = red @green = green @blue = blue end generates the following syntax errors: syntax error, unexpected '\n', expecting tCOLON2 or '[' or '.' syntax error, unexpected kEND, expecting $end I believe the correct syntax for the above code is the following: class RGBColor def initialize(red=0, green=0, blue=0) @red = red @green = green @blue = blue end end (368) 5th line from the top of the page; There's a blank space missing between the word "must" and "satisfy". change at the end of the 5th line of code ".... '#{method}' must" + to ".... '#{method}' must " + [373] Code at the bottom of the page; The following is wrong: invalid_xml = %{ Wheat } (valid_xml?(invalid_xml) == nil) According to the book, the above is supposed to return # => false, # That is, it is "valid" That is wrong. The valid_xml?(invalid_xml) returns nil. As such, the following statement does not work: REXML::Document.new(invalid_xml).write However, the following does work REXML::Document.new(good_xml).write {379} 1st line of code under Discussion; The following code is incorrect: red_fish = doc.children[0].children[3].children[1].children[1] The correct code is the following: red_fish = doc.children[1].children[3].children[1].children[1] Note: doc.children[0] refers to the text '\n' (i.e., the newline between "%{" and "" specified in the assignment to the variable xml on the top of page 378) {380} 3rd line of code from top of page; doc.children[0] is wrong doc.children[1] is correct doc.children[0] returns => "\n" doc.children[1] returns => ... (391) Second line; second line: require causes exception; ArgumentError: wrong number of arguments (0 for 1) Probably a misprint - should be eliminated???? [508] 2nd paragraph (code listing); The code gives an example how one could run many DNS queires in parallel. Unfortunately this code doesn't close the connection. This is now Problem in the example, but could lead to serious problems, if run for more then the 26 queries in the example. {558} Invocation line after $ cd status; Invocation "./script/generate controller status index" should be "ruby script/generate controller status index" Similar error on p.559 where "./script/server" should be "ruby script/server" {575} 4th paragraph (code); In the last code of recipe 15.7, "require 'active_support/core_ext'" should be replaced by "require 'active_support'". If "core_ext" is directly loaded, the search path will not be correctly set and "xml_simple" will not be found. Actually "active_support" will automatically load "active_support/core_ext". So the correct requires are: require 'rubygems' require 'active_support' {595,596,597} all code containing 'form_tag' and 'form_for'; form_tag and form_for always go inside "<% %>" tags AFAIK. They used to go into "<%= %>" tags, tho I don't remember when this change took place (somewhere between AWDR v. 1 and v.2) {662} Solution; During the third run of the divide.rb script, the error output says the script that errored was 'divide_buggy.rb' in three spots of the exception. In effect, the calling script and erroring script should be the same. Like such: $ ruby -d divide.rb Dividing 53 by 0 Exception `ZeroDivisionError' at divide.rb:6 - divided by 0 divide.rb:6:in `/': divided by 0 (ZeroDivisionError) from divide.rb:6 {662} Solution; In order to get the results described by --debug switch, the user must pass the switch to Ruby and not the script. All instances that look like '$ ./divide.rb --debug' should be '$ ruby --debug divide.rb' otherwise the interpreter never enters the debug state. {760} Second code excerpt in the Solution; The code given completes straightaway, not after 5 seconds as described in the text and simulated output. Either change the described output to fit the code, or change the code to fit the output -- perhaps like this: start_time = Time.now [7,8,9].each_simultaneously do |e| sleep(5) print "Completed operation for #{e}!\n" end.each { |t| t.join } # THIS IS THE CHANGED LINE ...[rest of code excerpt]... (630) Recipe 16.7. Using a WSDL File to Make SOAP Calls Easier; I got this book to help with SOAP/WSDL issues in Ruby, and Recipe 16.7 is great, but Google is no longer issuing keys for this API, so I can't try it out to try to figure out why it is working and my code isn't. Is an update to this recipe going to be posted using some other web service? (e.g. Amazon) (637) 2nd complete code block; On site 637 th following code is printed: "DRb.start_service('druby://127.0.0.1:61676', Threadsafe.new) But on site 636 the class is defined as ThreadsafeHash