From: "Jesús Gabriel y Galán" Date: 2011-05-06T00:02:34+09:00 Subject: Re: Creating variables on an OpenStruct with dynamic names Sorry, pressed wrong key... 2011/5/5 Jesús Gabriel y Galán : > On Thu, May 5, 2011 at 4:19 PM, Jolyon R. wrote: >> I think I was averse to creating multiple classes in one file, so >> thought it best to try and create the CsvRow inline, it had crossed my >> mind but I got hung up on the semantics of the book asking for an >> object. >> >> I create a CsvRow class and am now getting an each to return a CsvRow, >> AWESOME! >> >> The next bit I'm getting a little caught up on is how to get CsvRow to >> return a single value in a row based on a headers value. >> >> When I store a line in my CsvRow like this: >> >> @fields = line.chomp.split(', ') >> >> Is that an array or hash? > > You can read about String#split in the documentation and you will see > that it returns an array. > >> >> Using method_missing and it's an array will this work: >> >> class CsvRow >>  attr_reader :fields, :headersPattern > > Convention in Ruby is snake_case, so headers_pattern is more typical. > >>  def initialize(line) >>    @fields = line.chomp.split(', ') >>  end >> >>  def headerPattern( pattern ) >>    @headersPattern = pattern >>  end > > Who is calling this method? If you use attr_accessor instead of > reader, you get a method called header_pattern=, which sets the value, > as you are doing, so that would be more convenient. Also, you are > rewriting the method headerPattern created by attr_reader with this > one. So, remove it and use attr_accessor :header_pattern. > >>  def self.method_missing name, *args >>    @headersPattern.each_with_index |header, index| >>      if header = name.to_s yield @fields.send(@headers[index]) >>    end >>  end >> end > > method_missing is an instance method, so drop the self there.  And I > think that you want to access the corresponding field of the header in > the @fields array, so no need to send anything, just: > > def method_missing name, *args >  @header_pattern.each_with_index do |header, index| >    if header == name.to_s >      @fields[index] >      break >    end > end > > Which could be also written as: > > def method_missing name, *args >  if index = @header_pattern.index name >    @fields[index] >  end > end > > Which will find the name in the header_pattern, if it finds it indexes > the @fields array at that index. Anyway, if the numbers of headers > could be big, or the number of times you are going to call this method > is relevant, I would think of having a different data structure. I > would have a hash whose keys are the header names, and whose values > are what you have in @fields. If possible, I would pass the headers > when you construct the CsvRow, I think it's cleaner: > class CsvRow attr_reader :header_pattern, :values def initialize headers, line @header_pattern = headers @values = line.chomp.split(",") @hash = Hash[*@header_pattern.zip(@values).flatten] end def method_missing name, *args @hash[name] end end Hope this helps, Jesus.