Seven Languages in Seven Weeks – Ruby (Final Day, Lesson 3)

And so it is that I’ve finished the Ruby chapter of the Seven Languages book. The final challenge to your skills and sanity in the Ruby chapter of “Seven Languages in Seven Weeks” is as follows:

  • Modify the CSV application (demonstrated as an example earlier in the chapter) to support a each method to return a CsvRow object (not demonstrated earlier). Use the method_missing on that CsvRow to return the value for the column for a given heading. For example, for the file:

    one, two
    lions, tigers

    Allow an API that works like this:
    csv = RubyCsv.new
    csv.each{|row| puts row.one}

    This should print “lions”

Well…

It took me a lot of futzing around with this before I got it working, and the reason was strikingly similar to what was happening before, except that it was in the opposite direction. Whereas yesterday my problem was an unwanted implicit coercion from Hash to Array, today’s problem was that Ruby required an absolutely explicit coersion into String from values that should by rights have already been strings.

# Do :
#  Modify the CSV application to support an each
#  method to return a CsvRow object.  Use method_missing
#  on that CsvRow to return the value for the column
#  for a given heading.  For example, for the file:
#
#  one, two
#  lions, tigers
#
#  allow an API that works like this:
#  csv = RubyCsv.new
#  csv.each{|row| puts row.one}
#  this should print "lions"
 
class CsvRow
 
  def method_missing name, *args
 
    num = nil
    i = 0
    while i < @headers.length
      # puts "testing #{name.to_s} vs #{@headers[i].to_s} is #{name.to_s.eql?(@headers[i].to_s)}"
 
      # You mean to tell me that Ruby doesn't automagically coerce correctly to string here?
      # I have to do that myself?
      num = i if name.to_s == @headers[i].to_s
      i = i + 1
 
    end
 
 
    # This feels ugly.
    # I am sure there's a
    # prettier way to return 
    # the value, but I'm
    # drawing a blank.
 
    if num.nil?
      nil #Don't crash by accessing an array location with nil!
    else
      @row[num]
    end
 
  end
 
 
 
  attr_accessor :row
 
  def initialize(row_array, headers)
    # puts " row init #{row_array}"
    @row = row_array
    @headers = headers
  end
end
 
module ActsAsCsv
 
  def self.included(base)
    base.extend ClassMethods
  end
 
  module ClassMethods
    def acts_as_csv
      include InstanceMethods
    end
  end
 
  module InstanceMethods
    def read
      @csv_contents = []
      filename = self.class.to_s.downcase + '.txt'
      file = File.new(filename)
      @headers = file.gets.chomp.split(', ')
 
      file.each do |row|
        puts "initing with row #{row}"
        @csv_contents.push( CsvRow.new(row.chomp.split(', '), @headers) )
      end
    end
 
    def each(&block)
      @csv_contents.each do |row| 
        block.call row
      end
    end
 
    attr_accessor :headers, :csv_contents
 
    def initialize
      read
    end
  end
 
end
 
class RubyCsv #no inheritance.  mixing it in.
  include ActsAsCsv
  acts_as_csv
end
 
m = RubyCsv.new
m.each {|row| puts " col one #{row.one}"}
m.each {|row| puts " col two #{row.two}"}
m.each {|row| puts " col blah #{row.blah}"} 
# as a bonus, the program won't crash if you pass an invalid column header.

I feel conflicted still. Ruby seems to want to allow you to write everything as implicitly as possible. This is nice at times, right up until you run into a situation where the implicit action carried out by the machine is completely unexpected. I have to say it also feels incredibly weird and confining to have the “last statemet” executed in a method be its return value. That’s how you end up with really awkward constructs like the return statement at the end of the overridden method_missing method.

Those of you who don’t have the book probably don’t have a reference for the amount of modification I had to make to the ActsAsCsv module. The effort was very small. It really just consisted of writing that def each(&block) method and figuring out how to apply it to all the rows in the csv_contents, and chaging the read function to pass along the headers to the CsvRow instance.

I have to say that the idea of modules and mixins is very attractive. It’s certainly a more attractive solution to the problem of extending classes at runtime with kludges like Decorators, though I feel like the particular example listed might be something that you’d simply plan for with composition in ActionScript rather than try to work in as an inheritance-based solution. Still though, this is an area in which AS3 and Java are both very weak by comparison.

The author of the book makes no secret that Ruby is his favorite language, and that he came from a Java background. His feeling is that Ruby made it fun to program again after years of fighting against Java’s restrictive structure. This isn’t the first person I’ve heard this from. Indeed, I have a suspicion that the current prevailing trend towards completely dynamic and weakly typed languages is driven by a collective desire to stop speaking in Java’s courtly etiquette, and that because Java is onerous that all strong and static type systems must be.

Those of us from the Flash world know that this is not so, and that a balance can certainly be achieved, if only there were interest in finding it.

Tags:

  1. #1 written by fire April 2nd, 2011 at 06:40

    hi,

    i’m also on my path through the book. I think I found a somewhat smarter solution to the csv problem.

    take a look here
    https://github.com/xfire/seven_languages_in_seven_weeks/blob/master/ruby/rubycsv.rb

    there are two interesting sections:
    @csv_contents < make an Hash from the header fields and the values of a single line

    and:
    class < anonymous class bound to a variable

    hth,
    fire

  2. #2 written by fire April 2nd, 2011 at 06:46

    @ fire
    args, something eat up some parts. damn escaping 😉

    here they are…

    there are two interesting sections:
    @csv_contents << Hash[@headers.zip(row)]
    -> make an Hash from the header fields and the values of a single line

    and:
    class << row
    -> anonymous class bound to a variable

    hth,
    fire

  3. #3 written by The Horseman April 3rd, 2011 at 06:53

    @ fire
    I’ll admit it that the << operator in this respect didn't make a whole lot of sense to me and initially wasn't doing what I'd expected it would. Even crazier, I don't see a listing in the Ruby API about the << operator when used on a class definition.

    Nice solution.

  4. #4 written by fire April 3rd, 2011 at 07:04

    @ The Horseman
    << for an Array is defined in the API Doc (http://www.ruby-doc.org/core/classes/Array.html#M000225)

    “Object-Specific Classes” documentation can be found here: http://www.ruby-doc.org/docs/ProgrammingRuby/html/classes.html

    though, I find these things first in various blogs. 😉