7 Languages in 7 Weeks. Week 1: Ruby Day 3

Mary, Meta

Today Mary Ruby goes Meta. We explored some techniques for using Ruby to create, er, more Ruby.

My previous posts on the 7 Languages in 7 Weeks have been of an unsustainable length so I am going to aim for more succinct offerings for the rest of the exercise.

Metaprogramming

I confess I’m still waiting for the penny to drop with metaprogramming. To me it still just looks like any other language feature. And yet there are great claims made about it’s utility in re-use and constructing DSLs. The former is clear but the path to the latter is still occluded to me. More examples and applications of it in the wild will hopefully bring me enlightenment.

Metaprogramming is presented as a way of using Ruby to write Ruby. After a mere three days learning the language I find this description unhelpful. Surely we don’t want to get into eval’ing strings of Ruby? And, indeed, none of the samples and exercises we explored went down this road. So what’s up with this description?

What we can do with metaprogramming and Ruby’s open classes is package up code wholly independent of it’s eventual “host object” allowing us to focus completely on the module we are implementing.

The Exercises

We are provided with a simple CSV parsing application and our task is to modify it such that each row is now encapsulated in a new CsvRow object that allows access to each field via method_missing. This will allow us to write the following:

csvParser = RubyCsv.New
csvParser.each do |row|
   puts "Field1 value: #{row.Field1}"
end

This is my solution to this problem. The latest version can be found on github. The file it uses as input is also available on github.

#!/usr/bin/ruby

module ActsAsCsv
  
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    def acts_as_csv
      include InstanceMethods
    end
  end
  
  module InstanceMethods
    
    def prepare
      @filename = self.class.to_s.downcase + '.txt'
      File.open(@filename) do |csv_file|
         @headers = csv_file.gets.chomp.split(', ')
      end
    end

    def _read_headers
    end

    def each
       File.open(@filename) do |csv_file|
          csv_file.gets #skip headers
          csv_file.each do |row|
             yield CsvRow.new(@headers, row.chomp.split(', '))
          end
       end
    end
    
    attr_accessor :headers
    attr :filename
    
    def initialize
      prepare
    end

  end

end

class CsvRow

   attr :row_data

   def initialize(headers, row)
      @row_data = {}
      
      i = 0
      headers.each do |header|
         @row_data[header] = row[i]
         i += 1
      end
   end

   def method_missing name, *args
      @row_data[name.to_s]
   end
end

class RubyCsv # no inheritance! You can mix it in
  include ActsAsCsv
  acts_as_csv
end

m = RubyCsv.new
puts m.headers.inspect
m.each do |csvrow|
   puts "one: #{csvrow.one}"
   puts "two: #{csvrow.two}"
   puts "three: #{csvrow.three}"
end

Teh Codez

As ever the github area for day 3.

Advertisement

2 thoughts on “7 Languages in 7 Weeks. Week 1: Ruby Day 3

  1. I struggle with that definition of “metaprogramming” too. What would you say to the following alternative definition?

    Normal programming is using code to manipulate data.
    Metaprogramming is using code to manipulate code.

    That avoids defining metaprogramming in terms of “writing” code which, as you point out, has connotations of string evaluation.

    • Yeah, that’s definitely better.

      My main issue, that which is frustrating me most, is that I still can’t see what the fuss is about with metaprogramming.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s