From: "Jesús Gabriel y Galán" Date: 2011-05-05T02:14:05+09:00 Subject: Re: Creating variables on an OpenStruct with dynamic names On Wed, May 4, 2011 at 6:34 PM, Jolyon R. wrote: > Hey guys, > > I'm day 3 into The Pragmatic Programmers - Seven Languages in Seven > Days, having previously done a little ruby. I'm from an Actionscript > background and finding Ruby a little challenging, so bear with me. > > I've been parsing some very simple CSV, I've seen there are libraries > out there to handle this, but the book is about learning. > > The book gives us some simple code and the task is to elaborate on it: > > 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| >        @csv_contents << row.chomp.split(', ') >      end >    end Here you are not closing the file handler. As you are reading the whole file in memory anyway, I'd do it like this: def read filename = "#{self.class.to_s.downcase}.txt" @headers, *@csv_contents = File.readlines(filename).map {|line| line.chomp.split(",")} end >    attr_accessor :headers, :csv_contents > >    def initialize >      read >    end > >  end > > end > > class RubyCsv  # no inheritance! You can mix it in >  include ActsAsCsv >  acts_as_csv > end > > m = RubyCsv.new > puts m.headers.inspect > puts m.csv_contents.inspect > > The task is to create an *each* method and return a CsvRow object > instead of an array. > > So I figure I need to create the object first in the file.each loop > > This mess is where I got to: > > file.each do |row| >        @headers.each_with_index do |head, index| >          require 'ostruct' You should require outside of the loop. >          rowObject = OpenStruct.new >          rowObject.#{@headers[index]} = row.chomp.split(', ') > >        end >        @csv_contents << rowObject >      end > > But as you can see it's a mess > > I've also read some other posts around the internet and on here and > thought this might work but haven't tried it yet > > file.each_with_index do |row, rowIndex| >    @headers.each_with_index do |head, headIndex| >        require 'ostruct' >        rowObject = OpenStruct.new( :#{@header[headIndex => > row[rowIndex]} > > Or something... > > All help and guidance gratefully received. Then, for the CsvRow stuff: I don't get why you need an OpenStruct, because you are using the same headers for all rows. So maybe a normal Struct could work for you: class RubyCsv include ActsAsCsv acts_as_csv def each row_struct = Struct.new *@headers.map(&:to_sym) @csv_contents.each do |row| yield row_struct.new *row end end end csv = RubyCsv.new csv.read csv.each do |row| puts row end Maybe you would like to change how the file is read, maybe lazily in the each method, or in an initialize method so that all instances read the file at creation time. Having to call read to correctly initialize the object looks a bit awkward. Hope this helps, Jesus.