From: ara.t.howard@... Date: 2006-03-26T15:16:15+09:00 Subject: Re: [ANN] Article: An Exercise in Metaprogramming with Ruby On Sun, 26 Mar 2006, Joel VanderWerf wrote: > The point about coupling (mentioned in the second-to-last paragraph of the > article) is important, and I feel it is dismissed to easily in the article. > There are some tradeoffs to consider, though perhaps they are out of the > scope of the article, which is intended as an exercise, not as a complete > guide: > > 1. Suppose your code needs to _discover_ what fields are in the file? You > can use #instance_methods(false), but that is not perfect: you have to > filter out "to_s" and "inspect", which were added by #make. And what if you > add a new method in addition to the ones generated by #make? The field names > could be stored in a list kept in class instance variable... > > 2. Once you have discovered the field names, you have to use send(fieldname) > and send("#{fieldname}=") to access them. That's more awkward and (at least > in the second case) less efficient than Hash#[] and #[]=. Who's the "second > class citizen" in this case? > > 3. If you really know the field names "in advance" (that is, you have enough > information to hard code them into your program), rather than "by > discovery", then maybe it is better to use a different metaprogramming style > in which the fields are declared using class methods: > > class Person > field :name, :favorite_ice_cream, ... > end > > In this way, some rudimentary error checking can be performed when reading > the file, rather than failing later when trying to serve ice cream to the > person. (I just hate it when my ruby-scripted robo-fridge serves me passion > fruit and rabbit dropping ice cream.) This is not always the best way to go > (what if, as the article points out, fields get added to the file?), but one > more thing to keep in mind. i'm totally with you on this joel. still, i think one can have a bit of both: harp:~ > cat a.rb require "arrayfields" require "csv" csv = <<-csv latitude,longitude,description 47.23,59.34,Omaha 32.17,39.24,New York City 73.11,48.91,Carlsbad Caverns csv class CSVTable < ::Array attr "fields" def initialize arg CSV::parse(arg) do |row| row.map!{|c| c.to_s} if @fields self << row else @row_class = Class::new(::Array) do define_method("initialize") do |a| self.fields = row replace a end end @fields = row end end @fields.each{|field| column_attr field} end def << row super @row_class::new(row) end def column_attr(ca) singleton_class = class << self; self; end singleton_class.module_eval{ define_method(ca){ map{|r| r[ca]}} } end def [](*a, &b) m = a.first return(send(m)) if [String, Symbol].map{|c| c === m}.any? && respond_to?(m) super end end table = CSVTable::new csv p table puts p table.fields puts table.fields.each{|f| puts "#{ f }: #{ table[f].join(', ') }"} puts table.each{|row| puts row.fields.map{|f| "#{ f }: #{ row[f] }"}.join(', ') } puts harp:~ > ruby a.rb [["47.23", "59.34", "Omaha"], ["32.17", "39.24", "New York City"], ["73.11", "48.91", "Carlsbad Caverns"]] ["latitude", "longitude", "description"] latitude: 47.23, 32.17, 73.11 longitude: 59.34, 39.24, "48.91 description: Omaha, New York City, Carlsbad Caverns latitude: 47.23, longitude: 59.34, description: Omaha latitude: 32.17, longitude: 39.24, description: New York City latitude: 73.11, longitude: 48.91, description: Carlsbad Caverns regards. -a -- share your knowledge. it's a way to achieve immortality. - h.h. the 14th dali lama