csv_madness 0.0.6 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of csv_madness might be problematic. Click here for more details.

Files changed (54) hide show
  1. checksums.yaml +5 -13
  2. data/.document +5 -0
  3. data/CHANGELOG.markdown +4 -0
  4. data/Gemfile +2 -8
  5. data/README.rdoc +55 -47
  6. data/Rakefile +10 -49
  7. data/VERSION +1 -1
  8. data/lib/csv_madness/builder.rb +95 -10
  9. data/lib/csv_madness/column.rb +30 -0
  10. data/lib/csv_madness/column_methods/core_methods.rb +6 -0
  11. data/lib/csv_madness/column_methods/default_value_methods.rb +7 -0
  12. data/lib/csv_madness/column_methods/error_methods.rb +19 -0
  13. data/lib/csv_madness/column_methods/fetch_methods.rb +11 -0
  14. data/lib/csv_madness/column_methods/typing_methods.rb +24 -0
  15. data/lib/csv_madness/column_set.rb +5 -0
  16. data/lib/csv_madness/column_type.rb +74 -0
  17. data/lib/csv_madness/column_types/date.rb +14 -0
  18. data/lib/csv_madness/column_types/float.rb +25 -0
  19. data/lib/csv_madness/column_types/integer.rb +17 -0
  20. data/lib/csv_madness/column_types/string.rb +9 -0
  21. data/lib/csv_madness/data_accessor_module.rb +79 -44
  22. data/lib/csv_madness/exceptions.rb +16 -0
  23. data/lib/csv_madness/gem_api.rb +11 -2
  24. data/lib/csv_madness/index_set.rb +36 -0
  25. data/lib/csv_madness/indexer.rb +105 -0
  26. data/lib/csv_madness/record.rb +68 -40
  27. data/lib/csv_madness/record_filter.rb +68 -0
  28. data/lib/csv_madness/record_methods/method_methods.rb +57 -0
  29. data/lib/csv_madness/sheet.rb +172 -442
  30. data/lib/csv_madness/sheet_methods/base.rb +6 -0
  31. data/lib/csv_madness/sheet_methods/class_methods.rb +84 -0
  32. data/lib/csv_madness/sheet_methods/column_methods.rb +175 -0
  33. data/lib/csv_madness/sheet_methods/file_methods.rb +44 -0
  34. data/lib/csv_madness/sheet_methods/indexing.rb +133 -0
  35. data/lib/csv_madness/sheet_methods/record_methods.rb +175 -0
  36. data/lib/csv_madness/sheet_methods/sorting_methods.rb +10 -0
  37. data/lib/csv_madness/utils/const_proc.rb +19 -0
  38. data/lib/csv_madness/utils/counter.rb +24 -0
  39. data/lib/csv_madness/utils/counter_proc.rb +31 -0
  40. data/lib/csv_madness/utils/looker_upper.rb +36 -0
  41. data/lib/csv_madness.rb +2 -2
  42. data/test/csv/nil_headers.csv +5 -0
  43. data/test/csv/out/.gitkeep +0 -0
  44. data/test/csv/pokemon.csv +9 -0
  45. data/test/csv/test_column_types.csv +2 -2
  46. data/test/helper.rb +13 -5
  47. data/test/test_builder.rb +43 -3
  48. data/test/test_csv_madness.rb +34 -25
  49. data/test/test_index_set.rb +30 -0
  50. data/test/test_indexer.rb +50 -0
  51. data/test/test_reloading_spreadsheet.rb +5 -2
  52. data/test/test_sheet.rb +184 -37
  53. data/test/test_utils.rb +99 -0
  54. metadata +82 -36
@@ -1,144 +1,29 @@
1
1
  module CsvMadness
2
2
  class Sheet
3
- COLUMN_TYPES = {
4
- number: Proc.new do |cell, record|
5
- rval = cell
6
-
7
- unless cell.nil? || (cell.is_a?(String) && cell.length == 0)
8
-
9
- begin
10
- rval = Integer(cell)
11
- rescue
12
- # do nothing
13
- end
14
-
15
- unless rval.is_a?(Integer)
16
- begin
17
- rval = Float(cell)
18
- rescue
19
- # do nothing
20
- end
21
- end
22
- end
23
-
24
- rval
25
- end,
26
-
27
- integer: Proc.new do |cell, record|
28
- begin
29
- Integer(cell)
30
- rescue
31
- cell
32
- end
33
- end,
34
-
35
- float: Proc.new do |cell, record|
36
- begin
37
- Float(cell)
38
- rescue
39
- cell
40
- end
41
- end,
42
-
43
- date: Proc.new do |cell, record|
44
- begin
45
- parse = Time.parse( cell || "" )
46
- rescue ArgumentError
47
- if cell =~ /^Invalid Time Format: /
48
- parse = cell
49
- else
50
- parse = "Invalid Time Format: <#{cell}>"
51
- end
52
- end
53
-
54
- parse
55
- end
56
- }
57
-
58
- FORBIDDEN_COLUMN_NAMES = [:to_s] # breaks things hard when you use them. Probably not comprehensive, sadly.
59
-
60
- # Used to make getter/setter names out of the original header strings.
61
- # " hello;: world! " => :hello_world
62
- def self.getter_name( name )
63
- name = name.strip.gsub( /\s+/, "_" ).gsub( /(\W|_)+/, "_" ).downcase
64
- name = name.gsub( /_+$/, "" )
65
- if name.match( /^\d/ )
66
- name = "_#{name}"
67
- end
68
-
69
- name.to_sym
70
- end
71
-
72
-
73
- # Paths to be searched when CsvMadness.load( "filename.csv" ) is called.
74
- def self.add_search_path( path )
75
- @search_paths ||= []
76
- path = Pathname.new( path ).expand_path
77
- unless path.directory?
78
- raise "The given path does not exist"
79
- end
80
-
81
- @search_paths << path unless @search_paths.include?( path )
82
- end
83
-
84
- def self.search_paths
85
- @search_paths
86
- end
3
+ extend SheetMethods::ClassMethods
4
+ include SheetMethods::Base
5
+ # include SheetMethods::Indexing # TODO: May never re-implement
6
+ include SheetMethods::FileMethods
7
+ include SheetMethods::ColumnMethods
8
+ include SheetMethods::RecordMethods
9
+ include SheetMethods::SortingMethods
87
10
 
88
- def self.from( csv_file, opts = {} )
89
- if f = find_spreadsheet_in_filesystem( csv_file )
90
- Sheet.new( f, opts )
91
- else
92
- raise "File not found."
93
- end
94
- end
95
-
96
- # Search absolute/relative-to-current-dir before checking
97
- # search paths.
98
- def self.find_spreadsheet_in_filesystem( name )
99
- @search_paths ||= []
100
-
101
- expanded_path = Pathname.new( name ).expand_path
102
- if expanded_path.exist?
103
- return expanded_path
104
- else # look for it in the search paths
105
- @search_paths.each do |p|
106
- file = p.join( name )
107
- if file.exist? && file.file?
108
- return p.join( name )
109
- end
110
- end
111
- end
112
-
113
- nil
11
+ def indexing_enabled?
12
+ false
114
13
  end
115
14
 
116
- # opts are passed to underlying CSV (:row_sep, :encoding, :force_quotes)
117
- def self.to_csv( spreadsheet, opts = {} )
118
- out = spreadsheet.columns.to_csv( opts )
119
- spreadsheet.records.inject( out ) do |output, record|
120
- output << record.to_csv( opts )
121
- end
122
- end
15
+ attr_reader :columns, :column_mapping, :opts, :extra_methods
16
+ attr_accessor :spreadsheet_file
123
17
 
124
- def self.write_to_file( spreadsheet, file, opts = {} )
125
- file = file.fwf_filepath.expand_path
126
- File.open( file, "w" ) do |f|
127
- f << spreadsheet.to_csv( opts )
128
- end
129
- end
130
-
131
- def write_to_file( file, opts = {} )
132
- self.class.write_to_file( self, file, opts )
133
- end
134
-
135
- def to_csv( opts = {} )
136
- self.records.inject( self.columns.to_csv( opts ) ) do |output, record|
137
- output << record.to_csv( opts )
138
- end
139
- end
140
-
141
- attr_reader :columns, :index_columns, :records, :spreadsheet_file, :record_class, :opts
18
+ # new( <optional non-hash first argument>, <optional hash of options> )
19
+ # firstarg:
20
+ # nil or no first argument given: this Spreadsheet object isn't associated with a .csv file
21
+ #
22
+ # string or path: create Spreadsheet by reading contents of a CSV file
23
+ #
24
+ # array: sets the columns. Overlaps with opts[:columns], and should likely be removed.
25
+ #
26
+ #
142
27
  # opts:
143
28
  # index: ( [:id, :id2 ] )
144
29
  # columns you want mapped for quick
@@ -154,16 +39,25 @@ module CsvMadness
154
39
  # header: false
155
40
  # anything else, we assume the csv file has a header row
156
41
  def initialize( *args )
42
+ @csv = nil
43
+ @extra_methods = {} # key = method name as symbol, val: a proc that takes a record as an argument, and possibly a block
44
+ if indexing_enabled?
45
+ @index_columns = []
46
+ @indexes = {}
47
+ end
48
+
157
49
  if args.last.is_a?(Hash)
158
50
  @opts = args.pop
159
51
  else
160
52
  @opts = {}
161
53
  end
162
54
 
55
+ @column_types = {}
56
+
163
57
  firstarg = args.shift
164
58
 
165
59
  case firstarg
166
- when NilClass
60
+ when NilClass # Blank sheet, no affiliation with an existing csv file
167
61
  @spreadsheet_file = nil
168
62
  @opts[:columns] ||= []
169
63
  when String, FunWith::Files::FilePath, Pathname
@@ -178,146 +72,102 @@ module CsvMadness
178
72
  reload_spreadsheet
179
73
  end
180
74
 
181
- def <<( record )
182
- self.add_record( record )
183
- end
184
75
 
185
- def add_record( record )
186
- case record
187
- when Array
188
- # CSV::Row.new( column names, column_entries ) (in same order as columns, natch)
189
- record = CSV::Row.new( self.columns, record )
190
- when Hash
191
- header = []
192
- fields = []
193
-
194
- for col in self.columns
195
- header << col
196
- fields << record[col]
197
- end
198
-
199
- record = CSV::Row.new( header, fields )
200
- when CSV::Row
201
- # do nothing
76
+ def call_record_method( record, method, args )
77
+ if extra_method?( method )
78
+ # call the decorated method
79
+ call_extra_method( method, record, *args )
202
80
  else
203
- raise "sheet.add_record() doesn't take objects of type #{record.inspect}" unless record.respond_to?(:csv_data)
204
- record = record.csv_data
205
- end
206
-
207
- record = @record_class.new( record )
208
- @records << record
209
- add_to_indexes( record )
210
- end
211
-
212
- # record can be the row number (integer from 0...@records.length)
213
- # record can be the record itself (anonymous class)
214
- def remove_record( record )
215
- record = @records[record] if record.is_a?(Integer)
216
- return if record.nil?
217
-
218
- self.remove_from_index( record )
219
- @records.delete( record )
220
- end
221
-
222
- # Here's the deal: you hand us a block, and we'll remove the records for which
223
- # it yields _true_.
224
- def remove_records( records = nil, &block )
225
- if block_given?
226
- for record in @records
227
- remove_record( record ) if yield( record ) == true
228
- end
229
- else # records should be an array
230
- for record in records
231
- self.remove_record( record )
81
+ if method[-1] == '='
82
+ key = method[0..-2]
83
+ set_cell( record, key, args.first )
84
+ else
85
+ get_cell( record, method )
232
86
  end
233
87
  end
234
88
  end
235
-
236
- def reload_spreadsheet( opts = @opts )
237
- load_csv if @spreadsheet_file
238
- set_initial_columns( opts[:columns] )
239
- create_record_class
240
- package
89
+
90
+ def get_cell( record, key )
91
+ record = self[record] if record.is_a?( Integer )
92
+
93
+ index = if key.is_a?( Integer )
94
+ key
95
+ elsif has_column?( key )
96
+ index_of_column( key )
97
+ else
98
+ nil
99
+ end
241
100
 
242
- set_index_columns( opts[:index] )
243
- reindex
101
+ raise ColumnError.new( "Column #{key} does not exist. Column mapping: #{self.column_mapping.inspect}" ) if index.nil?
102
+
103
+ record.data[ index ]
244
104
  end
105
+
106
+
107
+ # TODO: Also update the index if necessary
108
+ def set_cell( record, key, val )
109
+ record = self[record] if record.is_a?( Integer )
245
110
 
246
- def set_index_columns( index_columns )
247
- @index_columns = case index_columns
248
- when NilClass
249
- []
250
- when Symbol
251
- [ index_columns ]
252
- when Array
253
- index_columns
254
- end
111
+ record.data[ index_of_column( key ) ] = val
255
112
  end
256
113
 
257
- def [] offset
258
- @records[offset]
259
- end
260
-
261
- # Fetches an indexed record based on the column indexed and the keying object.
262
- # If key is an array of keying objects, returns an array of records in the
263
- # same order that the keying objects appear.
264
- # Index column should yield a different, unique value for each record.
265
- def fetch( index_col, key )
266
- if key.is_a?(Array)
267
- key.map{ |key| @indexes[index_col][key] }
268
- else
269
- @indexes[index_col][key]
114
+ def to_csv( hash_opts = {}, **keyword_opts )
115
+ opts = hash_opts.merge( keyword_opts )
116
+ self.records.inject( self.columns.to_csv( ** opts ) ) do |output, record|
117
+ output << record.to_csv( ** opts )
270
118
  end
271
119
  end
272
-
273
- # function should take an object, and return either true or false
274
- # returns an array of objects that respond true when put through the
275
- # meat grinder
276
- def filter( &block )
277
- rval = []
278
- @records.each do |record|
279
- rval << record if ( yield record )
280
- end
281
-
282
- rval
120
+
121
+ def [] offset
122
+ @records[offset]
283
123
  end
284
124
 
285
- # removes rows which fail the given test from the spreadsheet.
286
- def filter!( &block )
287
- @records = self.filter( &block )
288
- reindex
289
- @records
125
+ def records
126
+ @records ||= []
290
127
  end
291
-
128
+
292
129
 
293
130
  # give a copy of the current spreadsheet, but with no records
294
131
  def blanked()
295
132
  sheet = self.class.new
296
133
  sheet.columns = @columns.clone
297
- sheet.index_columns = @index_columns.clone
134
+ sheet.set_column_mapping
135
+
136
+ sheet.index_columns = @index_columns.clone if indexing_enabled?
137
+
298
138
  sheet.records = []
299
139
  sheet.spreadsheet_file = nil
300
- sheet.create_data_accessor_module
301
- sheet.create_record_class
140
+ # sheet.create_data_accessor_module
141
+ # sheet.create_record_class
302
142
  sheet.opts = @opts.clone
303
- sheet.reindex
143
+ sheet.reindex if indexing_enabled?
304
144
 
305
145
  sheet
306
146
  end
307
147
 
308
- # give a block, and get back a hash.
309
- # The hash keys are the results of the block.
310
- # The hash values are copies of the spreadsheets, with only the records
311
- # which caused the block to return the key.
312
- def split( &block )
313
- sheets = Hash.new
148
+ # block form:
149
+ # give a block, and get back a hash.
150
+ # The hash keys are the results of the block.
151
+ # The hash values are copies of the spreadsheets, with only the records
152
+ # which caused the block to return the key.
153
+ #
154
+ # arg form:
155
+ # assumes the given arg is a column name
156
+ def split( col = nil, &block )
157
+ if block_given?
158
+ sheets = Hash.new
314
159
 
315
- for record in @records
316
- result_key = yield record
317
- ( sheets[result_key] ||= self.blanked() ) << record
318
- end
160
+ for record in @records
161
+ result_key = yield record
162
+ ( sheets[result_key] ||= self.blanked() ) << record
163
+ end
319
164
 
320
- sheets
165
+ sheets
166
+ elsif has_column?( col )
167
+ self.split(&col)
168
+ else
169
+ raise ColumnError.new( "No block given and column doesn't exist: #{col.inspect}" )
170
+ end
321
171
  # sheet_args = self.blanked
322
172
  # for key, record_set in records
323
173
  # sheet = self.clone
@@ -329,18 +179,6 @@ module CsvMadness
329
179
  # records
330
180
  end
331
181
 
332
- def column col
333
- @records.map(&col)
334
- end
335
-
336
- # retrieve multiple columns. Returns an array of the form
337
- # [ [record1:col1, record1:col2...], [record2:col1, record2:col2...] [...] ]
338
- def multiple_columns(*args)
339
- @records.inject([]){ |memo, record|
340
- memo << args.map{ |arg| record.send(arg) }
341
- memo
342
- }
343
- end
344
182
 
345
183
  # if blank is defined, only the records which are non-blank in that
346
184
  # column will actually be yielded. The rest will be set to the provided
@@ -350,102 +188,6 @@ module CsvMadness
350
188
  alter_column( column, blank, &block )
351
189
  end
352
190
  end
353
-
354
- def alter_column( column, blank = :undefined, &block )
355
- raise "Column does not exist: #{column}" unless @columns.include?( column )
356
-
357
- if cindex = @columns.index( column )
358
- for record in @records
359
- if record.blank?(column) && blank != :undefined
360
- record[cindex] = blank
361
- else
362
- record[cindex] = yield( record[cindex], record )
363
- end
364
- end
365
- end
366
- end
367
-
368
- # If no block given, adds an empty column
369
- def add_column( column, &block )
370
- raise "Column already exists: #{column}" if @columns.include?( column )
371
- raise "#{column} is in the list FORBIDDEN_COLUMN_NAMES" if FORBIDDEN_COLUMN_NAMES.include?(column)
372
- @columns << column
373
-
374
- # add empty column to each row
375
- @records.map{ |r|
376
- r.csv_data << {column => ""}
377
- }
378
-
379
- update_data_accessor_module
380
-
381
- if block_given?
382
- alter_column( column ) do |val, record|
383
- yield val, record
384
- end
385
- end
386
- end
387
-
388
- def drop_column( column )
389
- raise "Column does not exist" unless @columns.include?( column )
390
-
391
- @columns.delete( column )
392
-
393
- key = column.to_s
394
-
395
- @records.map{ |r|
396
- r.csv_data.delete( key )
397
- }
398
-
399
- update_data_accessor_module
400
- end
401
-
402
- def rename_column( column, new_name )
403
- @columns[@columns.index(column)] = new_name
404
- rename_index_column( column, new_name ) if @index_columns.include?( column )
405
- update_data_accessor_module
406
- end
407
-
408
- def set_column_type( column, type, blank = :undefined )
409
- alter_column( column, blank, &COLUMN_TYPES[type] )
410
- end
411
-
412
-
413
- # If :reverse_merge is true, then the dest column is only overwritten for records where :dest is blank
414
- def merge_columns( source, dest, opts = {} )
415
- opts = { :drop_source => true, :reverse_merge => false, :default => "" }.merge( opts )
416
- column_must_exist( source, dest )
417
-
418
- self.records.each do |record|
419
- if opts[:reverse_merge] == false || record.blank?( dest )
420
- record[dest] = record.blank?(source) ? opts[:default] : record[source]
421
- end
422
- end
423
-
424
- self.drop_column( source ) if opts[:drop_source]
425
- end
426
-
427
- # By default, the
428
- def concat_columns( col1, col2, opts = {} )
429
- opts = {:separator => '', :out => col1}.merge( opts )
430
-
431
- column_must_exist( col1, col2 )
432
- self.add_column( opts[:out] ) unless self.columns.include?( opts[:out] )
433
-
434
- for record in self.records
435
- record[ opts[:out] ] = "#{record[col1]}#{opts[:separator]}#{record[col2]}"
436
- end
437
- end
438
-
439
- alias :concatenate :concat_columns
440
-
441
- # Note: If a block is given, the mod arg will be ignored.
442
- def add_record_methods( mod = nil, &block )
443
- if block_given?
444
- mod = Module.new( &block )
445
- end
446
- @record_class.send( :include, mod )
447
- self
448
- end
449
191
 
450
192
  # Note: If implementation of Record[] changes, so must this.
451
193
  def nils_are_blank_strings
@@ -459,74 +201,50 @@ module CsvMadness
459
201
  end
460
202
 
461
203
  protected
462
- attr_writer :columns, :index_columns, :records, :spreadsheet_file, :record_class, :opts
204
+ attr_writer :columns, :records, :record_class, :opts
463
205
 
464
206
  def load_csv
207
+ puts "Options: #{@opts.inspect}"
465
208
 
466
209
  # encoding seems to solve a specific problem with a specific spreadsheet, at an unknown cost.
467
- @csv = CSV.new( File.read(@spreadsheet_file).force_encoding("ISO-8859-1").encode("UTF-8"),
468
- { write_headers: true,
469
- headers: ( @opts[:header] ? :first_row : false ) } )
470
- end
210
+ data = File.read(@spreadsheet_file).force_encoding("ISO-8859-1").encode("UTF-8")
471
211
 
472
- def add_to_index( col, key, record )
473
- (@indexes[col] ||= {})[key] = record
474
- end
475
-
476
- def add_to_indexes( records )
477
- if records.is_a?( Array )
478
- for record in records
479
- add_to_indexes( record )
480
- end
481
- else
482
- record = records
483
- for col in @index_columns
484
- add_to_index( col, record.send(col), record )
485
- end
486
- end
487
- end
488
-
489
- def remove_from_index( record )
490
- for col in @index_columns
491
- @indexes[col].delete( record.send(col) )
492
- end
493
- end
494
-
495
- # Reindexes the record lookup tables.
496
- def reindex
497
- @indexes = {}
498
- add_to_indexes( @records )
499
- end
500
-
501
- # shouldn't require reindex
502
- def rename_index_column( column, new_name )
503
- @index_columns[ @index_columns.index( column ) ] = new_name
504
- @indexes[new_name] = @indexes[column]
505
- @indexes.delete(column)
506
- end
507
-
508
- # Each spreadsheet has its own anonymous record class, and each CSV row instantiates
509
- # a record of this class. This is where the getters and setters come from.
510
- def create_record_class
511
- create_data_accessor_module
512
- @record_class = Class.new( CsvMadness::Record )
513
- @record_class.spreadsheet = self
514
- @record_class.send( :include, @module )
212
+ header_flag = @opts[:header] ? :first_row : false
213
+ @csv = CSV.new( data, write_headers: true, headers: header_flag ) # , { write_headers: true, headers: header_flag } ) API changed with CSV 3.x
515
214
  end
215
+
216
+ # No longer needed.
217
+ # # Each spreadsheet has its own anonymous record class, and each CSV row instantiates
218
+ # # a record of this class. This is where the getters and setters come from.
219
+ # def create_record_class
220
+ # create_data_accessor_module
221
+ # @record_class = Class.new( CsvMadness::Record )
222
+ # @record_class.spreadsheet = self
223
+ # @record_class.send( :include, @module )
224
+ # end
516
225
 
517
226
  # fetch the original headers from the CSV file. If opts[:headers] is false,
518
227
  # or the CSV file isn't loaded yet, returns an empty array.
228
+ #
229
+ #
230
+ # I think that somewhere between v2 and v3 of CSV, they changed the way headers get loaded.
231
+ # For whatever reason, something's broken here.
519
232
  def fetch_csv_headers
520
233
  if @csv && @opts[:header]
521
- if @csv.headers == true
522
- @csv.shift
523
- headers = @csv.headers
234
+ if @csv.headers.is_a?(Array)
235
+ headers = @csv.headers # already loaded
236
+ elsif @csv.header_row?
237
+ # WARNING: This is probably a really expensive way to get the headers...
238
+ headers = @csv.read.headers.map(&:to_sym)
524
239
  @csv.rewind # shift/rewind, else @csv.headers only returns 'true'
525
240
  else
526
- headers = @csv.headers
241
+ warn( "NO HEADERS in CSV" )
242
+ headers = @csv.headers || []
527
243
  end
244
+
528
245
  headers
529
246
  else
247
+ warn( "THE ELSE HAPPENED")
530
248
  []
531
249
  end
532
250
  end
@@ -537,25 +255,35 @@ module CsvMadness
537
255
  # otherwise, you end up with accessors like record.col1, record.col2, record.col3...
538
256
  # If the columns given doesn't match the number of columns in the spreadsheet
539
257
  # prints a warning and a comparison of the columns to the headers.
540
- def set_initial_columns( columns = nil )
258
+ def set_columns( columns = nil )
541
259
  if columns.nil?
542
260
  if @opts[:header] == false
543
- columns = (0...csv_column_count).map{ |i| :"col#{i}" }
261
+ columns = (0...csv_column_count).map{ |i| self.class.default_column_name(i) }
544
262
  else
545
- columns = fetch_csv_headers.map{ |name| self.class.getter_name( name ) }
263
+ header_list = fetch_csv_headers
264
+ columns = header_list.each_with_index.map do |name, index|
265
+ self.class.getter_name( name || self.class.default_column_name( index ) )
266
+ end
546
267
  end
547
268
  else
548
269
  unless !@csv || columns.length == csv_column_count
549
270
  $stderr.puts "Warning <#{@spreadsheet_file}>: columns array does not match the number of columns in the spreadsheet."
271
+ @columns = columns
550
272
  compare_columns_to_headers
551
273
  end
552
274
  end
553
-
554
- for column in columns
555
- raise "#{column} is in the list FORBIDDEN_COLUMN_NAMES" if FORBIDDEN_COLUMN_NAMES.include?(column)
556
- end
557
-
275
+
276
+ raise_on_forbidden_column_names( columns )
277
+
558
278
  @columns = columns
279
+ set_column_mapping
280
+ end
281
+
282
+ def set_column_mapping
283
+ @column_mapping = @columns.each_with_index.inject( {} ) do |memo, col_with_index|
284
+ memo[col_with_index.first.to_sym] = col_with_index.last
285
+ memo
286
+ end
559
287
  end
560
288
 
561
289
  # Printout so the user can see which CSV columns are being matched to which
@@ -563,18 +291,19 @@ module CsvMadness
563
291
  # column list.
564
292
  def compare_columns_to_headers
565
293
  headers = fetch_csv_headers
566
-
567
- for i in 0...([@columns, headers].map(&:length).max)
568
- $stdout.puts "\t#{i}: #{@columns[i]} ==> #{headers[i]}"
294
+ max_index = ([@columns, headers].map(&:length).max)
295
+
296
+ for i in 0 ... max_index
297
+ $stdout.puts "\t#{i}: #{@columns[i].inspect} ==> #{headers[i].inspect}"
569
298
  end
570
299
  end
571
300
 
572
301
 
573
302
  # Create objects that respond to the recipe-named methods
574
- def package
303
+ def package_each_csv_row
575
304
  @records = []
576
305
  (@csv || []).each do |row|
577
- @records << @record_class.new( row )
306
+ @records << Record.new( row, self )
578
307
  end
579
308
  end
580
309
 
@@ -583,27 +312,28 @@ module CsvMadness
583
312
  fetch_csv_headers.length
584
313
  end
585
314
 
586
- def columns_to_mapping
587
- @columns.each_with_index.inject({}){ |memo, item|
588
- memo[item.first] = item.last
589
- memo
590
- }
591
- end
592
-
593
- def create_data_accessor_module
594
- # columns = @columns # yes, this line is necessary. Module.new has its own @vars.
595
-
596
- @module = DataAccessorModule.new( columns_to_mapping )
597
- end
598
-
599
- def update_data_accessor_module
600
- @module.remap_accessors( columns_to_mapping )
601
- end
602
-
603
- def column_must_exist( *cols )
604
- for col in cols
605
- raise ArgumentError.new( "#{caller[0]}: column :#{col} does not exist.") unless self.columns.include?(col)
606
- end
607
- end
315
+ # # returns a mapping based off the current ordered list of columns.
316
+ # # A hash where the first column
317
+ # def columns_to_mapping
318
+ # @columns.each_with_index.inject({}){ |memo, item|
319
+ # memo[item.first] = item.last
320
+ # memo
321
+ # }
322
+ # end
323
+ #
324
+ # def create_data_accessor_module
325
+ # # columns = @columns # yes, this line is necessary. Module.new has its own @vars.
326
+ #
327
+ # @module = DataAccessorModule.new( columns_to_mapping )
328
+ # end
329
+ #
330
+ # def update_data_accessor_module
331
+ # debugger
332
+ # @module.remap_accessors( columns_to_mapping )
333
+ # end
334
+ #
335
+ # def column_accessors_map
336
+ # @module.column_accessors_map
337
+ # end
608
338
  end
609
339
  end