From: Robert Klemme Date: 2011-05-05T17:57:59+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. Welcome to the wonderful world of Ruby! > 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 > >    attr_accessor :headers, :csv_contents > >    def initialize >      read >    end > >  end > > end Note that the approach above has the disadvantage to read the whole file into memory and store it there. This could use up a lot memory if the file is large - or even fail. Often a better approach is to use Ruby's block mechanism to yield one row at a time. Then you can still use that to create an Array of all rows. Silly example (a row is just a number here): irb(main):019:0> def rows irb(main):020:1> if block_given? irb(main):021:2> yield 1 irb(main):022:2> yield 2 irb(main):023:2> yield 3 irb(main):024:2> else irb(main):025:2* a = [] irb(main):026:2> rows {|r| a << r} irb(main):027:2> a irb(main):028:2> end irb(main):029:1> end => nil irb(main):030:0> rows {|r| p r} 1 2 3 => 3 irb(main):031:0> rows => [1, 2, 3] If there is no block rows invokes itself providing a block which appends to the Array. > 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' >          rowObject = OpenStruct.new >          rowObject.#{@headers[index]} = row.chomp.split(', ') This will assigne the whole row as an Array to a single member of OpenStruct. I don't think this is what you want. Rather, you want multiple assignments. You could do it like this: # get all the columns columns = row.chomp.split(', ') # create the row object row = OpenStruct.new # now assign all values by combining # headers with columns @headers.zip columns do |hd, col| row.send("#{hd}=", col) end Another approach would first create a Hash and use OpenStruct's Hash construction: irb(main):004:0> h = {"foo" => 1, "bar" => 2} => {"foo"=>1, "bar"=>2} irb(main):005:0> o = OpenStruct.new(h) => # irb(main):006:0> o.foo => 1 irb(main):007:0> o.bar => 2 For that you could do # get all the columns columns = row.chomp.split(', ') # combine into nested arrays and create Hash h = Hash[@headers.zip columns] # now create the row object row = OpenStruct.new h >        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. Hopefully that helped. Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/