<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
  <title>PaulBarry.com - Practical Common Ruby</title>
  <subtitle type="html">My thoughts, ideas, questions and concerns on technology, sports, music and life</subtitle>
  <id>tag:paulbarry.com,2007:Paulbarry.com</id>
  <generator uri="http://www.paulbarry.com" version="3.0">PaulBarry.com</generator>
  <link href="http://paulbarry.com/xml/atom/article/4848/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="http://paulbarry.com/articles/2007/11/25/practical-common-ruby" rel="alternate" type="text/html"/>

  <updated>2009-01-05T22:19:13-05:00</updated>
  <entry>
    <author>
      <name>Paul Barry</name>
      <email>mail@paulbarry.com</email>
    </author>
    <id>urn:uuid:de4c9ce3-53a9-4834-b8e0-d017fe2deb97</id>

    <published>2007-11-25T12:04:21-05:00</published>
    <updated>2007-11-25T12:04:21-05:00</updated>
    <title type="html">Practical Common Ruby</title>
    <link href="http://paulbarry.com/articles/2007/11/25/practical-common-ruby" rel="alternate" type="text/html"/>

    <category term="technology" scheme="http://paulbarry.com/articles/category/technology" label="Technology"/>
        <category term="lisp" scheme="http://paulbarry.com/articles/tag/lisp"/>
    <category term="Ruby" scheme="http://paulbarry.com/articles/tag/ruby"/>
        <summary type="html">&lt;p&gt;As part of my latest effort to &lt;a href=&quot;http://paulbarry.com/articles/2007/11/23/lisp-my-personal-nbl&quot;&gt;learn Lisp&lt;/a&gt;, I&apos;m going through &lt;a href=&quot;http://www.gigamonkeys.com/book&quot;&gt;Practical Common Lisp&lt;/a&gt;.  After reading chapter 3, I thought it would be a cool idea to translate that chapter into a language I&apos;m familiar with, Ruby.  I&apos;m assuming it will help point out some of the powerful aspects of Lisp, showing how accomplishing the same thing in Ruby is more difficult.  So pull up &lt;a href=&quot;http://www.gigamonkeys.com/book/practical-a-simple-database.html&quot;&gt;Chapter 3&lt;/a&gt; and follow along at home.&lt;/p&gt;

&lt;p&gt;In order to follow along, what I&apos;m doing is using irb, Ruby&apos;s version of the Lisp REPL, and saving the code into a script.  So create a file called &lt;code&gt;chap3.rb&lt;/code&gt; or whatever, and then fire up irb in that same directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now your script is loaded and you can call whatever methods you have defined.  Also, you can run &lt;code&gt;load &apos;chap3.rb&apos;&lt;/code&gt; again and it will reload the script.&lt;/p&gt;

&lt;p&gt;So right of the bat, one different we run into is the lack of a property list in Ruby.  So in Lisp you have this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CL-USER&amp;gt; (getf (list :a 1 :b :c 3) :a)
1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It has properties of both a &lt;a href=&quot;http://www.ruby-doc.org/core/classes/Array.html&quot;&gt;Ruby Array&lt;/a&gt; and a &lt;a href=&quot;http://www.ruby-doc.org/core/classes/Hash.html&quot;&gt;Ruby Hash&lt;/a&gt;.  It&apos;s just a list of items, but it&apos;s like a Hash in that you can lookup a value by it&apos;s key, using the &lt;code&gt;getf&lt;/code&gt; function as shown above.  So in Ruby, we could use a Hash:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):001:0&amp;gt; {:a =&amp;gt; 1, :b =&amp;gt; 2, :c =&amp;gt; 3}[:a]
=&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Which works for the lookup part, but doesn&apos;t preserve insertion order.  If we need insertion order, we could use an Array of Arrays and write our own getf function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def getf(plist, key)
  plist.each do |k, v|
    return v if key == k
  end
  nil
end

irb(main):007:0&amp;gt; getf([[:a, 1], [:b, 2], [:c, 3]], :a)
=&amp;gt; 1
irb(main):008:0&amp;gt; getf([[:a, 1], [:b, 2], [:c, 3]], :d)
=&amp;gt; nil
irb(main):009:0&amp;gt; getf([[:a, 1], [:b, 2], [:c, 3]], :b)
=&amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case, I don&apos;t think insertion order is necessary, so we&apos;ll just use an Array of Hashes, as it&apos;s a little more a part of Ruby.&lt;/p&gt;

&lt;p&gt;Now that that&apos;s out of the way, we&apos;ll set up the global database and the function to make cds:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$db = []

def make_cd(title, artist, rating, ripped)
  {:title =&amp;gt; title, :artist =&amp;gt; artist, :rating =&amp;gt; rating, :ripped =&amp;gt; ripped}
end

def add_record(cd)
  $db &amp;lt;&amp;lt; cd
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, this is just a straight port of the code, which I&apos;ll try to stick to throughout.  Next up is the &lt;code&gt;dump-db&lt;/code&gt; function.  Now one little trick that Lisp&apos;s format function has is to be able iterate through a list within the format string.  This I can say Ruby doesn&apos;t have, and it&apos;s probably due to the fact that lists are so much a part of Lisp.  Here&apos;s a more simple example of how it works:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CL-USER&amp;gt; (format t &quot;~{~a~%~}&quot; &apos;(1 2 3))
1
2 
3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each element of the format string is preceded by a &lt;code&gt;~&lt;/code&gt; character.  So it starts with &lt;code&gt;~{&lt;/code&gt;, which indicates we&apos;re working with an element that is a list.  The &lt;code&gt;~}&lt;/code&gt; at end just closes the list of things we are doing to each item.  The &lt;code&gt;~a&lt;/code&gt; element just effectively prints the argument and the &lt;code&gt;~%&lt;/code&gt; prints a new line character. &lt;/p&gt;

&lt;p&gt;As the author points out, this isn&apos;t all that different from the ruby &lt;code&gt;%&lt;/code&gt; format operator, except for the functionality to iterate over a list.  So we&apos;ll have to do that ourselves in the Ruby version, but luckily it&apos;s not that much work.  Here&apos;s our first take at it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def dump_db
  $db.each do |cd|
    cd.each do |k,v|
      puts &quot;%-10s%s&quot; % [&quot;#{k.to_s.upcase}:&quot;, v]
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here&apos;s what that results in:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
irb(main):002:0&amp;gt; add_record(make_cd(&apos;Largo&apos;,&apos;Brad Mehldau&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}]
irb(main):003:0&amp;gt; add_record(make_cd(&apos;Junta&apos;,&apos;Phish&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}, 
    {:artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Junta&quot;}]
irb(main):004:0&amp;gt; dump_db
ARTIST:   Brad Mehldau
RATING:   9
RIPPED:   true
TITLE:    Largo

ARTIST:   Phish
RATING:   9
RIPPED:   true
TITLE:    Junta
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This works except for one issue, the items print out an arbitrary order, since Ruby&apos;s Hash doesn&apos;t preserve insertion order :(.  So we can specify the order in the function if that&apos;s something we care about:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def dump_db
  $db.each do |cd|
    %w{title artist rating ripped}.each do |f|
      puts &quot;%-10s%s&quot; % [&quot;#{f.upcase}:&quot;, cd[f.to_sym]]
    end
    print &quot;\n&quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But for the rest of the example I&apos;m going to stick with the random ordered version.  The reason I like that it that it prints out everything in the record, even if we add new fields.  &lt;/p&gt;

&lt;p&gt;So I&apos;m getting tired of re-adding the data, so I&apos;m going to jump down to saving and loading the data.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def save_db(filename)
  open(filename, &apos;w&apos;) do |file|
    file.puts $db.inspect
  end
end

def load_db(filename)
  $db = eval(open(filename){|f| f.read})
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here it is in action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
irb(main):002:0&amp;gt; add_record(make_cd(&apos;Largo&apos;,&apos;Brad Mehldau&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}]
irb(main):003:0&amp;gt; add_record(make_cd(&apos;Junta&apos;,&apos;Phish&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}, 
    {:artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Junta&quot;}]
irb(main):004:0&amp;gt; save_db &quot;my-cds.db&quot;
=&amp;gt; nil
irb(main):005:0&amp;gt; quit
paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
irb(main):002:0&amp;gt; load_db &quot;my-cds.db&quot;
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}, 
    {:artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Junta&quot;}]
irb(main):003:0&amp;gt; dump_db
ARTIST:   Brad Mehldau
RATING:   9
RIPPED:   true
TITLE:    Largo

ARTIST:   Phish
RATING:   9
RIPPED:   true
TITLE:    Junta
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So now we jump back up to &quot;Improving the User Interaction&quot;.  This basically translates directly into Ruby, except that we have to create our own &lt;code&gt;y_or_n_p&lt;/code&gt;, which is trivial:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def prompt_read(prompt)
  print &quot;#{prompt}: &quot;
  gets.chomp
end

def y_or_n_p(prompt)
  case prompt_read(prompt+&quot; [y/n]&quot;).upcase
  when &apos;Y&apos;: true
  when &apos;N&apos;: false
  else y_or_n_p(prompt)
  end
end

def prompt_for_cd
  make_cd(
    prompt_read(&quot;Title&quot;),
    prompt_read(&quot;Artist&quot;),
    prompt_read(&quot;Rating&quot;).to_i,
    y_or_n_p(&quot;Ripped&quot;)
  )
end

def add_cds
  loop do 
    add_record prompt_for_cd
    break unless y_or_n_p(&quot;Another?&quot;)
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here it is in action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):021:0&amp;gt; add_cds
Title: Wormwood
Artist: moe.
Rating: 10
Ripped [y/n]: y
Another? [y/n]: y
Title: Animals
Artist: Pink Floyd
Rating: 8
Ripped [y/n]: n
Another? [y/n]: n
=&amp;gt; nil
irb(main):022:0&amp;gt; dump_db
ARTIST:   Brad Mehldau
RATING:   9
RIPPED:   true
TITLE:    Largo

ARTIST:   Phish
RATING:   9
RIPPED:   true
TITLE:    Junta

ARTIST:   moe.
RATING:   10
RIPPED:   true
TITLE:    Wormwood

ARTIST:   Pink Floyd
RATING:   8
RIPPED:   false
TITLE:    Animals
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So on to querying the database.  We&apos;ll use  Ruby&apos;s &lt;code&gt;find_all&lt;/code&gt; iterator rather than Lisp&apos;s &lt;code&gt;remove-if-not&lt;/code&gt; function.  I&apos;ll go through each function as the chapter does.  First up, selecting an artist:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def select_by_artist(artist)
  $db.find_all{|cd| cd[:artist] == artist}
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So then we refactor that to pass the selector function (Proc, in Ruby terminology) into the method, and we have a method to create the selector:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def select(selector)
  $db.find_all{|cd| selector.call(cd) }
end

def artist_selector(artist)
  lambda{|cd| cd[:artist] == artist}
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We call this in Ruby like this:&lt;/p&gt;

&lt;p&gt;  irb(main):034:0&gt; select artist_selector(&quot;Phish&quot;)&lt;/p&gt;

&lt;p&gt;So next we build the &quot;where&quot; selector, and we&apos;ll use Ruby&apos;s Hash to stand in for keyword parameters, which are very similar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def where(p={})
  lambda do |cd|
    (p.has_key?(:title) ? cd[:title] == p[:title] : true) &amp;amp;&amp;amp;
    (p.has_key?(:artist) ? cd[:artist] == p[:artist] : true) &amp;amp;&amp;amp;
    (p.has_key?(:rating) ? cd[:rating] == p[:rating] : true) &amp;amp;&amp;amp;
    (p.has_key?(:ripped) ? cd[:ripped] == p[:ripped] : true)
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can call it like this to verify that it works:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):066:0&amp;gt; select where(:artist =&amp;gt; &quot;moe.&quot;, :rating =&amp;gt; 10)
=&amp;gt; [{:artist=&amp;gt;&quot;moe.&quot;, :rating=&amp;gt;10, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Wormwood&quot;}]
irb(main):067:0&amp;gt; select where(:artist =&amp;gt; &quot;moe.&quot;, :rating =&amp;gt; 9)
=&amp;gt; []
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By the way, jumping ahead a bit, there&apos;s no need to explicitly list each field in the where function.  We can simplify that down like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def where(p={})
  lambda do |cd|
    r = true
    p.each do |k,v|
      unless cd[k] == v
        r = false
        break
      end
    end
    r
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So now if you add fields to the cd record, you don&apos;t have to touch any of these methods.  Onto the update method.  This time we&apos;ll have Ruby&apos;s &lt;code&gt;each&lt;/code&gt; iterator stand in for Lisp&apos;s &lt;code&gt;mapcar&lt;/code&gt;.  Also, for the sake of simplicity, we&apos;ll modify the actual record in the database, instead of making a copy of the database and updating the global variable to point to the new database.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def update(selector, values={})
  $db.each do |row|
    if selector.call(row)
      values.each do |k,v|
        row[k] = v
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again, I feel this is considerably more readable than the Lisp version, plus it doesn&apos;t require explicitly listing each field.  I suppose readability is in the eye of the beholder.  I would imagine Lisp programmers find the end statements in the method above as annoying as Lisp new-comers find the parenthesis in Lisp code, but as you become familiar with the language and the syntax, those annoyances just fade away.  &lt;/p&gt;

&lt;p&gt;Here it is in action, after adding one more Phish album to the collection, which is never a bad thing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):014:0&amp;gt; select where(:artist =&amp;gt; &quot;Phish&quot;)
=&amp;gt; [{:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true}]
irb(main):015:0&amp;gt; add_cds
Title: Lawnboy
Artist: Phish
Rating: 8
Ripped [y/n]: y
Another? [y/n]: n
=&amp;gt; nil
irb(main):016:0&amp;gt; save_db &quot;my-cds.db&quot;
=&amp;gt; nil
irb(main):017:0&amp;gt; select where(:artist =&amp;gt; &quot;Phish&quot;)
=&amp;gt; [{:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Lawnboy&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;8, :ripped=&amp;gt;true}]
irb(main):018:0&amp;gt; update where(:artist =&amp;gt; &quot;Phish&quot;), :rating =&amp;gt; 7
=&amp;gt; [{:title=&amp;gt;&quot;Largo&quot;, :artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Wormwood&quot;, :artist=&amp;gt;&quot;moe.&quot;, :rating=&amp;gt;10, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Animals&quot;, :artist=&amp;gt;&quot;Pink Floyd&quot;, :rating=&amp;gt;8, :ripped=&amp;gt;false}, 
    {:title=&amp;gt;&quot;Lawnboy&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}]
irb(main):019:0&amp;gt; select where(:rating =&amp;gt; 7)
=&amp;gt; [{:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Lawnboy&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And for sake of completedness, here&apos;s the delete:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def delete(selector)
  $db.delete_if{|cd| selector.call(cd) }
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that gives us the whole thing weighing in at 88 lines.  It&apos;s longer than the Lisp version in terms of number of lines simply because the end statements sit on their own line.  We also don&apos;t have the duplication that is removed in the final section of this chapter, so that&apos;s not necessary.&lt;/p&gt;

&lt;p&gt;But the essence of the final section is macros, which seems to be one of the most unique and powerful features of Lisp.  In this particular chapter, we&apos;ve managed to write code that is just as powerful and maintainable, and possibly more readable, without macros.  But as I get deeper into Lisp, I&apos;m sure I&apos;ll find examples where that&apos;s not the case.  One observation I have from this so far is that I&apos;ve never used Ruby&apos;s clearly Lisp-inspired lambda feature in my day-to-day Rails work, but maybe I should be.&lt;/p&gt;
</summary>
    <content type="html">&lt;p&gt;As part of my latest effort to &lt;a href=&quot;http://paulbarry.com/articles/2007/11/23/lisp-my-personal-nbl&quot;&gt;learn Lisp&lt;/a&gt;, I&apos;m going through &lt;a href=&quot;http://www.gigamonkeys.com/book&quot;&gt;Practical Common Lisp&lt;/a&gt;.  After reading chapter 3, I thought it would be a cool idea to translate that chapter into a language I&apos;m familiar with, Ruby.  I&apos;m assuming it will help point out some of the powerful aspects of Lisp, showing how accomplishing the same thing in Ruby is more difficult.  So pull up &lt;a href=&quot;http://www.gigamonkeys.com/book/practical-a-simple-database.html&quot;&gt;Chapter 3&lt;/a&gt; and follow along at home.&lt;/p&gt;

&lt;p&gt;In order to follow along, what I&apos;m doing is using irb, Ruby&apos;s version of the Lisp REPL, and saving the code into a script.  So create a file called &lt;code&gt;chap3.rb&lt;/code&gt; or whatever, and then fire up irb in that same directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now your script is loaded and you can call whatever methods you have defined.  Also, you can run &lt;code&gt;load &apos;chap3.rb&apos;&lt;/code&gt; again and it will reload the script.&lt;/p&gt;

&lt;p&gt;So right of the bat, one different we run into is the lack of a property list in Ruby.  So in Lisp you have this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CL-USER&amp;gt; (getf (list :a 1 :b :c 3) :a)
1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It has properties of both a &lt;a href=&quot;http://www.ruby-doc.org/core/classes/Array.html&quot;&gt;Ruby Array&lt;/a&gt; and a &lt;a href=&quot;http://www.ruby-doc.org/core/classes/Hash.html&quot;&gt;Ruby Hash&lt;/a&gt;.  It&apos;s just a list of items, but it&apos;s like a Hash in that you can lookup a value by it&apos;s key, using the &lt;code&gt;getf&lt;/code&gt; function as shown above.  So in Ruby, we could use a Hash:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):001:0&amp;gt; {:a =&amp;gt; 1, :b =&amp;gt; 2, :c =&amp;gt; 3}[:a]
=&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Which works for the lookup part, but doesn&apos;t preserve insertion order.  If we need insertion order, we could use an Array of Arrays and write our own getf function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def getf(plist, key)
  plist.each do |k, v|
    return v if key == k
  end
  nil
end

irb(main):007:0&amp;gt; getf([[:a, 1], [:b, 2], [:c, 3]], :a)
=&amp;gt; 1
irb(main):008:0&amp;gt; getf([[:a, 1], [:b, 2], [:c, 3]], :d)
=&amp;gt; nil
irb(main):009:0&amp;gt; getf([[:a, 1], [:b, 2], [:c, 3]], :b)
=&amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case, I don&apos;t think insertion order is necessary, so we&apos;ll just use an Array of Hashes, as it&apos;s a little more a part of Ruby.&lt;/p&gt;

&lt;p&gt;Now that that&apos;s out of the way, we&apos;ll set up the global database and the function to make cds:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$db = []

def make_cd(title, artist, rating, ripped)
  {:title =&amp;gt; title, :artist =&amp;gt; artist, :rating =&amp;gt; rating, :ripped =&amp;gt; ripped}
end

def add_record(cd)
  $db &amp;lt;&amp;lt; cd
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, this is just a straight port of the code, which I&apos;ll try to stick to throughout.  Next up is the &lt;code&gt;dump-db&lt;/code&gt; function.  Now one little trick that Lisp&apos;s format function has is to be able iterate through a list within the format string.  This I can say Ruby doesn&apos;t have, and it&apos;s probably due to the fact that lists are so much a part of Lisp.  Here&apos;s a more simple example of how it works:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CL-USER&amp;gt; (format t &quot;~{~a~%~}&quot; &apos;(1 2 3))
1
2 
3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each element of the format string is preceded by a &lt;code&gt;~&lt;/code&gt; character.  So it starts with &lt;code&gt;~{&lt;/code&gt;, which indicates we&apos;re working with an element that is a list.  The &lt;code&gt;~}&lt;/code&gt; at end just closes the list of things we are doing to each item.  The &lt;code&gt;~a&lt;/code&gt; element just effectively prints the argument and the &lt;code&gt;~%&lt;/code&gt; prints a new line character. &lt;/p&gt;

&lt;p&gt;As the author points out, this isn&apos;t all that different from the ruby &lt;code&gt;%&lt;/code&gt; format operator, except for the functionality to iterate over a list.  So we&apos;ll have to do that ourselves in the Ruby version, but luckily it&apos;s not that much work.  Here&apos;s our first take at it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def dump_db
  $db.each do |cd|
    cd.each do |k,v|
      puts &quot;%-10s%s&quot; % [&quot;#{k.to_s.upcase}:&quot;, v]
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here&apos;s what that results in:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
irb(main):002:0&amp;gt; add_record(make_cd(&apos;Largo&apos;,&apos;Brad Mehldau&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}]
irb(main):003:0&amp;gt; add_record(make_cd(&apos;Junta&apos;,&apos;Phish&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}, 
    {:artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Junta&quot;}]
irb(main):004:0&amp;gt; dump_db
ARTIST:   Brad Mehldau
RATING:   9
RIPPED:   true
TITLE:    Largo

ARTIST:   Phish
RATING:   9
RIPPED:   true
TITLE:    Junta
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This works except for one issue, the items print out an arbitrary order, since Ruby&apos;s Hash doesn&apos;t preserve insertion order :(.  So we can specify the order in the function if that&apos;s something we care about:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def dump_db
  $db.each do |cd|
    %w{title artist rating ripped}.each do |f|
      puts &quot;%-10s%s&quot; % [&quot;#{f.upcase}:&quot;, cd[f.to_sym]]
    end
    print &quot;\n&quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But for the rest of the example I&apos;m going to stick with the random ordered version.  The reason I like that it that it prints out everything in the record, even if we add new fields.  &lt;/p&gt;

&lt;p&gt;So I&apos;m getting tired of re-adding the data, so I&apos;m going to jump down to saving and loading the data.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def save_db(filename)
  open(filename, &apos;w&apos;) do |file|
    file.puts $db.inspect
  end
end

def load_db(filename)
  $db = eval(open(filename){|f| f.read})
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here it is in action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
irb(main):002:0&amp;gt; add_record(make_cd(&apos;Largo&apos;,&apos;Brad Mehldau&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}]
irb(main):003:0&amp;gt; add_record(make_cd(&apos;Junta&apos;,&apos;Phish&apos;,9,true))
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}, 
    {:artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Junta&quot;}]
irb(main):004:0&amp;gt; save_db &quot;my-cds.db&quot;
=&amp;gt; nil
irb(main):005:0&amp;gt; quit
paulbarry@paulbarry: ~/projects/practical_common_ruby $ irb
irb(main):001:0&amp;gt; load &apos;chap3.rb&apos;
=&amp;gt; true
irb(main):002:0&amp;gt; load_db &quot;my-cds.db&quot;
=&amp;gt; [{:artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Largo&quot;}, 
    {:artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Junta&quot;}]
irb(main):003:0&amp;gt; dump_db
ARTIST:   Brad Mehldau
RATING:   9
RIPPED:   true
TITLE:    Largo

ARTIST:   Phish
RATING:   9
RIPPED:   true
TITLE:    Junta
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So now we jump back up to &quot;Improving the User Interaction&quot;.  This basically translates directly into Ruby, except that we have to create our own &lt;code&gt;y_or_n_p&lt;/code&gt;, which is trivial:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def prompt_read(prompt)
  print &quot;#{prompt}: &quot;
  gets.chomp
end

def y_or_n_p(prompt)
  case prompt_read(prompt+&quot; [y/n]&quot;).upcase
  when &apos;Y&apos;: true
  when &apos;N&apos;: false
  else y_or_n_p(prompt)
  end
end

def prompt_for_cd
  make_cd(
    prompt_read(&quot;Title&quot;),
    prompt_read(&quot;Artist&quot;),
    prompt_read(&quot;Rating&quot;).to_i,
    y_or_n_p(&quot;Ripped&quot;)
  )
end

def add_cds
  loop do 
    add_record prompt_for_cd
    break unless y_or_n_p(&quot;Another?&quot;)
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here it is in action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):021:0&amp;gt; add_cds
Title: Wormwood
Artist: moe.
Rating: 10
Ripped [y/n]: y
Another? [y/n]: y
Title: Animals
Artist: Pink Floyd
Rating: 8
Ripped [y/n]: n
Another? [y/n]: n
=&amp;gt; nil
irb(main):022:0&amp;gt; dump_db
ARTIST:   Brad Mehldau
RATING:   9
RIPPED:   true
TITLE:    Largo

ARTIST:   Phish
RATING:   9
RIPPED:   true
TITLE:    Junta

ARTIST:   moe.
RATING:   10
RIPPED:   true
TITLE:    Wormwood

ARTIST:   Pink Floyd
RATING:   8
RIPPED:   false
TITLE:    Animals
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So on to querying the database.  We&apos;ll use  Ruby&apos;s &lt;code&gt;find_all&lt;/code&gt; iterator rather than Lisp&apos;s &lt;code&gt;remove-if-not&lt;/code&gt; function.  I&apos;ll go through each function as the chapter does.  First up, selecting an artist:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def select_by_artist(artist)
  $db.find_all{|cd| cd[:artist] == artist}
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So then we refactor that to pass the selector function (Proc, in Ruby terminology) into the method, and we have a method to create the selector:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def select(selector)
  $db.find_all{|cd| selector.call(cd) }
end

def artist_selector(artist)
  lambda{|cd| cd[:artist] == artist}
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We call this in Ruby like this:&lt;/p&gt;

&lt;p&gt;  irb(main):034:0&gt; select artist_selector(&quot;Phish&quot;)&lt;/p&gt;

&lt;p&gt;So next we build the &quot;where&quot; selector, and we&apos;ll use Ruby&apos;s Hash to stand in for keyword parameters, which are very similar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def where(p={})
  lambda do |cd|
    (p.has_key?(:title) ? cd[:title] == p[:title] : true) &amp;amp;&amp;amp;
    (p.has_key?(:artist) ? cd[:artist] == p[:artist] : true) &amp;amp;&amp;amp;
    (p.has_key?(:rating) ? cd[:rating] == p[:rating] : true) &amp;amp;&amp;amp;
    (p.has_key?(:ripped) ? cd[:ripped] == p[:ripped] : true)
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can call it like this to verify that it works:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):066:0&amp;gt; select where(:artist =&amp;gt; &quot;moe.&quot;, :rating =&amp;gt; 10)
=&amp;gt; [{:artist=&amp;gt;&quot;moe.&quot;, :rating=&amp;gt;10, :ripped=&amp;gt;true, :title=&amp;gt;&quot;Wormwood&quot;}]
irb(main):067:0&amp;gt; select where(:artist =&amp;gt; &quot;moe.&quot;, :rating =&amp;gt; 9)
=&amp;gt; []
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By the way, jumping ahead a bit, there&apos;s no need to explicitly list each field in the where function.  We can simplify that down like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def where(p={})
  lambda do |cd|
    r = true
    p.each do |k,v|
      unless cd[k] == v
        r = false
        break
      end
    end
    r
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So now if you add fields to the cd record, you don&apos;t have to touch any of these methods.  Onto the update method.  This time we&apos;ll have Ruby&apos;s &lt;code&gt;each&lt;/code&gt; iterator stand in for Lisp&apos;s &lt;code&gt;mapcar&lt;/code&gt;.  Also, for the sake of simplicity, we&apos;ll modify the actual record in the database, instead of making a copy of the database and updating the global variable to point to the new database.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def update(selector, values={})
  $db.each do |row|
    if selector.call(row)
      values.each do |k,v|
        row[k] = v
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again, I feel this is considerably more readable than the Lisp version, plus it doesn&apos;t require explicitly listing each field.  I suppose readability is in the eye of the beholder.  I would imagine Lisp programmers find the end statements in the method above as annoying as Lisp new-comers find the parenthesis in Lisp code, but as you become familiar with the language and the syntax, those annoyances just fade away.  &lt;/p&gt;

&lt;p&gt;Here it is in action, after adding one more Phish album to the collection, which is never a bad thing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;irb(main):014:0&amp;gt; select where(:artist =&amp;gt; &quot;Phish&quot;)
=&amp;gt; [{:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true}]
irb(main):015:0&amp;gt; add_cds
Title: Lawnboy
Artist: Phish
Rating: 8
Ripped [y/n]: y
Another? [y/n]: n
=&amp;gt; nil
irb(main):016:0&amp;gt; save_db &quot;my-cds.db&quot;
=&amp;gt; nil
irb(main):017:0&amp;gt; select where(:artist =&amp;gt; &quot;Phish&quot;)
=&amp;gt; [{:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Lawnboy&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;8, :ripped=&amp;gt;true}]
irb(main):018:0&amp;gt; update where(:artist =&amp;gt; &quot;Phish&quot;), :rating =&amp;gt; 7
=&amp;gt; [{:title=&amp;gt;&quot;Largo&quot;, :artist=&amp;gt;&quot;Brad Mehldau&quot;, :rating=&amp;gt;9, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Wormwood&quot;, :artist=&amp;gt;&quot;moe.&quot;, :rating=&amp;gt;10, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Animals&quot;, :artist=&amp;gt;&quot;Pink Floyd&quot;, :rating=&amp;gt;8, :ripped=&amp;gt;false}, 
    {:title=&amp;gt;&quot;Lawnboy&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}]
irb(main):019:0&amp;gt; select where(:rating =&amp;gt; 7)
=&amp;gt; [{:title=&amp;gt;&quot;Junta&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}, 
    {:title=&amp;gt;&quot;Lawnboy&quot;, :artist=&amp;gt;&quot;Phish&quot;, :rating=&amp;gt;7, :ripped=&amp;gt;true}]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And for sake of completedness, here&apos;s the delete:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def delete(selector)
  $db.delete_if{|cd| selector.call(cd) }
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that gives us the whole thing weighing in at 88 lines.  It&apos;s longer than the Lisp version in terms of number of lines simply because the end statements sit on their own line.  We also don&apos;t have the duplication that is removed in the final section of this chapter, so that&apos;s not necessary.&lt;/p&gt;

&lt;p&gt;But the essence of the final section is macros, which seems to be one of the most unique and powerful features of Lisp.  In this particular chapter, we&apos;ve managed to write code that is just as powerful and maintainable, and possibly more readable, without macros.  But as I get deeper into Lisp, I&apos;m sure I&apos;ll find examples where that&apos;s not the case.  One observation I have from this so far is that I&apos;ve never used Ruby&apos;s clearly Lisp-inspired lambda feature in my day-to-day Rails work, but maybe I should be.&lt;/p&gt;
</content>
  </entry>
  </feed>