csv_madness 0.0.9 → 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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.document +5 -0
  3. data/CHANGELOG.markdown +4 -0
  4. data/Gemfile +2 -7
  5. data/Rakefile +12 -78
  6. data/VERSION +1 -1
  7. data/lib/csv_madness/builder.rb +91 -9
  8. data/lib/csv_madness/column.rb +30 -0
  9. data/lib/csv_madness/column_methods/core_methods.rb +6 -0
  10. data/lib/csv_madness/column_methods/default_value_methods.rb +7 -0
  11. data/lib/csv_madness/column_methods/error_methods.rb +19 -0
  12. data/lib/csv_madness/column_methods/fetch_methods.rb +11 -0
  13. data/lib/csv_madness/column_methods/typing_methods.rb +24 -0
  14. data/lib/csv_madness/column_set.rb +5 -0
  15. data/lib/csv_madness/column_type.rb +74 -0
  16. data/lib/csv_madness/column_types/date.rb +14 -0
  17. data/lib/csv_madness/column_types/float.rb +25 -0
  18. data/lib/csv_madness/column_types/integer.rb +17 -0
  19. data/lib/csv_madness/column_types/string.rb +9 -0
  20. data/lib/csv_madness/data_accessor_module.rb +79 -79
  21. data/lib/csv_madness/gem_api.rb +11 -2
  22. data/lib/csv_madness/index_set.rb +36 -0
  23. data/lib/csv_madness/indexer.rb +105 -0
  24. data/lib/csv_madness/record.rb +68 -42
  25. data/lib/csv_madness/record_filter.rb +68 -0
  26. data/lib/csv_madness/record_methods/method_methods.rb +57 -0
  27. data/lib/csv_madness/sheet.rb +160 -76
  28. data/lib/csv_madness/sheet_methods/class_methods.rb +11 -4
  29. data/lib/csv_madness/sheet_methods/column_methods.rb +95 -107
  30. data/lib/csv_madness/sheet_methods/file_methods.rb +9 -6
  31. data/lib/csv_madness/sheet_methods/indexing.rb +104 -23
  32. data/lib/csv_madness/sheet_methods/record_methods.rb +123 -47
  33. data/lib/csv_madness/sheet_methods/sorting_methods.rb +10 -0
  34. data/lib/csv_madness/utils/const_proc.rb +19 -0
  35. data/lib/csv_madness/utils/counter.rb +24 -0
  36. data/lib/csv_madness/utils/counter_proc.rb +31 -0
  37. data/lib/csv_madness/utils/looker_upper.rb +36 -0
  38. data/lib/csv_madness.rb +1 -0
  39. data/test/csv/nil_headers.csv +5 -0
  40. data/test/csv/out/.gitkeep +0 -0
  41. data/test/csv/pokemon.csv +9 -0
  42. data/test/csv/test_column_types.csv +2 -2
  43. data/test/helper.rb +9 -6
  44. data/test/test_builder.rb +36 -0
  45. data/test/test_csv_madness.rb +28 -18
  46. data/test/test_index_set.rb +30 -0
  47. data/test/test_indexer.rb +50 -0
  48. data/test/test_reloading_spreadsheet.rb +4 -2
  49. data/test/test_sheet.rb +164 -27
  50. data/test/test_utils.rb +99 -0
  51. metadata +66 -38
@@ -2,15 +2,28 @@ module CsvMadness
2
2
  class Sheet
3
3
  extend SheetMethods::ClassMethods
4
4
  include SheetMethods::Base
5
- include SheetMethods::Indexing
5
+ # include SheetMethods::Indexing # TODO: May never re-implement
6
6
  include SheetMethods::FileMethods
7
7
  include SheetMethods::ColumnMethods
8
8
  include SheetMethods::RecordMethods
9
+ include SheetMethods::SortingMethods
9
10
 
11
+ def indexing_enabled?
12
+ false
13
+ end
10
14
 
11
- attr_reader :columns, :index_columns, :record_class, :opts
15
+ attr_reader :columns, :column_mapping, :opts, :extra_methods
12
16
  attr_accessor :spreadsheet_file
13
17
 
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
+ #
14
27
  # opts:
15
28
  # index: ( [:id, :id2 ] )
16
29
  # columns you want mapped for quick
@@ -27,6 +40,11 @@ module CsvMadness
27
40
  # anything else, we assume the csv file has a header row
28
41
  def initialize( *args )
29
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
30
48
 
31
49
  if args.last.is_a?(Hash)
32
50
  @opts = args.pop
@@ -34,10 +52,12 @@ module CsvMadness
34
52
  @opts = {}
35
53
  end
36
54
 
55
+ @column_types = {}
56
+
37
57
  firstarg = args.shift
38
58
 
39
59
  case firstarg
40
- when NilClass
60
+ when NilClass # Blank sheet, no affiliation with an existing csv file
41
61
  @spreadsheet_file = nil
42
62
  @opts[:columns] ||= []
43
63
  when String, FunWith::Files::FilePath, Pathname
@@ -53,10 +73,48 @@ module CsvMadness
53
73
  end
54
74
 
55
75
 
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 )
80
+ else
81
+ if method[-1] == '='
82
+ key = method[0..-2]
83
+ set_cell( record, key, args.first )
84
+ else
85
+ get_cell( record, method )
86
+ end
87
+ end
88
+ end
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
100
+
101
+ raise ColumnError.new( "Column #{key} does not exist. Column mapping: #{self.column_mapping.inspect}" ) if index.nil?
102
+
103
+ record.data[ index ]
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 )
56
110
 
57
- def to_csv( opts = {} )
58
- self.records.inject( self.columns.to_csv( opts ) ) do |output, record|
59
- output << record.to_csv( opts )
111
+ record.data[ index_of_column( key ) ] = val
112
+ end
113
+
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 )
60
118
  end
61
119
  end
62
120
 
@@ -73,30 +131,43 @@ module CsvMadness
73
131
  def blanked()
74
132
  sheet = self.class.new
75
133
  sheet.columns = @columns.clone
76
- sheet.index_columns = @index_columns.clone
134
+ sheet.set_column_mapping
135
+
136
+ sheet.index_columns = @index_columns.clone if indexing_enabled?
137
+
77
138
  sheet.records = []
78
139
  sheet.spreadsheet_file = nil
79
- sheet.create_data_accessor_module
80
- sheet.create_record_class
140
+ # sheet.create_data_accessor_module
141
+ # sheet.create_record_class
81
142
  sheet.opts = @opts.clone
82
- sheet.reindex
143
+ sheet.reindex if indexing_enabled?
83
144
 
84
145
  sheet
85
146
  end
86
147
 
87
- # give a block, and get back a hash.
88
- # The hash keys are the results of the block.
89
- # The hash values are copies of the spreadsheets, with only the records
90
- # which caused the block to return the key.
91
- def split( &block )
92
- 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
93
159
 
94
- for record in @records
95
- result_key = yield record
96
- ( sheets[result_key] ||= self.blanked() ) << record
97
- end
160
+ for record in @records
161
+ result_key = yield record
162
+ ( sheets[result_key] ||= self.blanked() ) << record
163
+ end
98
164
 
99
- 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
100
171
  # sheet_args = self.blanked
101
172
  # for key, record_set in records
102
173
  # sheet = self.clone
@@ -117,16 +188,6 @@ module CsvMadness
117
188
  alter_column( column, blank, &block )
118
189
  end
119
190
  end
120
-
121
-
122
- # Note: If a block is given, the mod arg will be ignored.
123
- def add_record_methods( mod = nil, &block )
124
- if block_given?
125
- mod = Module.new( &block )
126
- end
127
- @record_class.send( :include, mod )
128
- self
129
- end
130
191
 
131
192
  # Note: If implementation of Record[] changes, so must this.
132
193
  def nils_are_blank_strings
@@ -140,38 +201,50 @@ module CsvMadness
140
201
  end
141
202
 
142
203
  protected
143
- attr_writer :columns, :index_columns, :records, :record_class, :opts
204
+ attr_writer :columns, :records, :record_class, :opts
144
205
 
145
206
  def load_csv
207
+ puts "Options: #{@opts.inspect}"
146
208
 
147
209
  # encoding seems to solve a specific problem with a specific spreadsheet, at an unknown cost.
148
- @csv = CSV.new( File.read(@spreadsheet_file).force_encoding("ISO-8859-1").encode("UTF-8"),
149
- { write_headers: true,
150
- headers: ( @opts[:header] ? :first_row : false ) } )
151
- end
152
-
153
- # Each spreadsheet has its own anonymous record class, and each CSV row instantiates
154
- # a record of this class. This is where the getters and setters come from.
155
- def create_record_class
156
- create_data_accessor_module
157
- @record_class = Class.new( CsvMadness::Record )
158
- @record_class.spreadsheet = self
159
- @record_class.send( :include, @module )
210
+ data = File.read(@spreadsheet_file).force_encoding("ISO-8859-1").encode("UTF-8")
211
+
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
160
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
161
225
 
162
226
  # fetch the original headers from the CSV file. If opts[:headers] is false,
163
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.
164
232
  def fetch_csv_headers
165
233
  if @csv && @opts[:header]
166
- if @csv.headers == true
167
- @csv.shift
168
- 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)
169
239
  @csv.rewind # shift/rewind, else @csv.headers only returns 'true'
170
240
  else
171
- headers = @csv.headers
241
+ warn( "NO HEADERS in CSV" )
242
+ headers = @csv.headers || []
172
243
  end
244
+
173
245
  headers
174
246
  else
247
+ warn( "THE ELSE HAPPENED")
175
248
  []
176
249
  end
177
250
  end
@@ -182,12 +255,15 @@ module CsvMadness
182
255
  # otherwise, you end up with accessors like record.col1, record.col2, record.col3...
183
256
  # If the columns given doesn't match the number of columns in the spreadsheet
184
257
  # prints a warning and a comparison of the columns to the headers.
185
- def set_initial_columns( columns = nil )
258
+ def set_columns( columns = nil )
186
259
  if columns.nil?
187
260
  if @opts[:header] == false
188
- columns = (0...csv_column_count).map{ |i| :"col#{i}" }
261
+ columns = (0...csv_column_count).map{ |i| self.class.default_column_name(i) }
189
262
  else
190
- 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
191
267
  end
192
268
  else
193
269
  unless !@csv || columns.length == csv_column_count
@@ -200,6 +276,14 @@ module CsvMadness
200
276
  raise_on_forbidden_column_names( columns )
201
277
 
202
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
203
287
  end
204
288
 
205
289
  # Printout so the user can see which CSV columns are being matched to which
@@ -216,10 +300,10 @@ module CsvMadness
216
300
 
217
301
 
218
302
  # Create objects that respond to the recipe-named methods
219
- def package
303
+ def package_each_csv_row
220
304
  @records = []
221
305
  (@csv || []).each do |row|
222
- @records << @record_class.new( row, self.column_accessors_map )
306
+ @records << Record.new( row, self )
223
307
  end
224
308
  end
225
309
 
@@ -228,28 +312,28 @@ module CsvMadness
228
312
  fetch_csv_headers.length
229
313
  end
230
314
 
231
- # returns a mapping based off the current ordered list of columns.
232
- # A hash where the first column
233
- def columns_to_mapping
234
- @columns.each_with_index.inject({}){ |memo, item|
235
- memo[item.first] = item.last
236
- memo
237
- }
238
- end
239
-
240
- def create_data_accessor_module
241
- # columns = @columns # yes, this line is necessary. Module.new has its own @vars.
242
-
243
- @module = DataAccessorModule.new( columns_to_mapping )
244
- end
245
-
246
- def update_data_accessor_module
247
- debugger
248
- @module.remap_accessors( columns_to_mapping )
249
- end
250
-
251
- def column_accessors_map
252
- @module.column_accessors_map
253
- 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
254
338
  end
255
339
  end
@@ -5,7 +5,9 @@ module CsvMadness
5
5
  # " hello;: world! " => :hello_world
6
6
  def getter_name( name )
7
7
  # replace any run of non-word characters with a single "_"
8
- name = name.downcase.gsub( /(\W|_|\s)+/, "_" )
8
+ return nil if name.nil?
9
+
10
+ name = name.downcase.to_s.gsub( /(\W|_|\s)+/, "_" )
9
11
 
10
12
  # snip trailing and leading "_"
11
13
  name = name.gsub( /(^_+|_+$)/, "" )
@@ -15,6 +17,10 @@ module CsvMadness
15
17
 
16
18
  name.to_sym
17
19
  end
20
+
21
+ def default_column_name( index )
22
+ "col#{index}".to_sym
23
+ end
18
24
 
19
25
 
20
26
  # Paths to be searched when CsvMadness.load( "filename.csv" ) is called.
@@ -28,9 +34,7 @@ module CsvMadness
28
34
  @search_paths << path unless @search_paths.include?( path )
29
35
  end
30
36
 
31
- def search_paths
32
- @search_paths
33
- end
37
+ attr_reader :search_paths
34
38
 
35
39
  def from( csv_file, opts = {} )
36
40
  if f = find_spreadsheet_in_filesystem( csv_file )
@@ -69,6 +73,9 @@ module CsvMadness
69
73
  end
70
74
 
71
75
  def write_to_file( spreadsheet, file, opts = {} )
76
+ opts = opts.clone
77
+ opts.delete( :columns ) # CSV library chokes on this option
78
+
72
79
  file = file.fwf_filepath.expand_path
73
80
  file.write( spreadsheet.to_csv( opts ) )
74
81
  end
@@ -1,67 +1,15 @@
1
1
  module CsvMadness
2
2
  module SheetMethods
3
- module ColumnMethods
4
-
5
- COLUMN_TYPES = {
6
- number: Proc.new do |cell, record|
7
- rval = cell
8
-
9
- unless cell.nil? || (cell.is_a?(String) && cell.length == 0)
10
-
11
- begin
12
- rval = Integer(cell)
13
- rescue
14
- # do nothing
15
- end
16
-
17
- unless rval.is_a?(Integer)
18
- begin
19
- rval = Float(cell)
20
- rescue
21
- # do nothing
22
- end
23
- end
24
- end
25
-
26
- rval
27
- end,
28
-
29
- integer: Proc.new do |cell, record|
30
- begin
31
- Integer(cell)
32
- rescue
33
- cell
34
- end
35
- end,
36
-
37
- float: Proc.new do |cell, record|
38
- begin
39
- Float(cell)
40
- rescue
41
- cell
42
- end
43
- end,
44
-
45
- date: Proc.new do |cell, record|
46
- begin
47
- parse = Time.parse( cell || "" )
48
- rescue ArgumentError
49
- if cell =~ /^Invalid Time Format: /
50
- parse = cell
51
- else
52
- parse = "Invalid Time Format: <#{cell}>"
53
- end
54
- end
55
-
56
- parse
57
- end
58
- }
59
-
3
+ module ColumnMethods
60
4
  FORBIDDEN_COLUMN_NAMES = [:to_s] # breaks things hard when you use them. Probably not comprehensive, sadly.
61
5
 
62
6
 
63
- def column col
64
- @records.map(&col)
7
+ # can only be called with proper column names (not extra methods)
8
+ def column( col )
9
+ index = index_of_column( col )
10
+
11
+ # Should be faster...
12
+ @records.map(&:data).map{ |dat| dat[index] }
65
13
  end
66
14
 
67
15
  def has_column?( col )
@@ -71,71 +19,81 @@ module CsvMadness
71
19
  # retrieve multiple columns. Returns an array of the form
72
20
  # [ [record1:col1, record1:col2...], [record2:col1, record2:col2...] [...] ]
73
21
  def multiple_columns(*args)
74
- @records.inject([]){ |memo, record|
75
- memo << args.map{ |arg| record.send(arg) }
76
- memo
77
- }
78
- end
79
-
80
- def alter_column( column, blank = :undefined, &block )
81
- raise_on_missing_columns( column )
82
-
83
- if cindex = @columns.index( column )
84
- for record in @records
85
- if record.blank?(column) && blank != :undefined
86
- record[cindex] = blank
87
- else
88
- record[cindex] = yield( record[cindex], record )
89
- end
22
+ column_results = args.map do |col|
23
+ index_of_column( col )
24
+ end
25
+
26
+ column_results.first.zip( *(column_results[1..-1] ) )
27
+ end
28
+
29
+ def alter_column( col, blank = :undefined, &block )
30
+ cindex = index_of_column( col )
31
+
32
+ for record in @records
33
+ if record.blank?( col ) && blank != :undefined
34
+ record[cindex] = blank
35
+ else
36
+ record[cindex] = yield( record[cindex], record )
90
37
  end
91
38
  end
92
39
  end
93
40
 
94
- # If no block given, adds an empty column.
41
+ # If no block given, adds an empty column (blank string).
95
42
  # If block given, fills the new column cells when the block is run on each record
96
43
  # TODO: Should be able to specify the index of the column
97
- def add_column( column, &block )
98
- raise_on_forbidden_column_names( column )
99
- raise ColumnExistsError.new( "Column already exists: #{column}" ) if @columns.include?( column )
44
+ def add_column( col, &block )
45
+ raise_on_forbidden_column_names( col )
46
+ raise_on_columns_present( col )
100
47
 
101
- @columns << column
48
+ @columns << col
102
49
 
103
50
  # add empty column to each row
104
- @records.map{ |r|
105
- r.csv_data << {column => ""}
106
- }
107
-
108
- update_data_accessor_module
109
-
110
- if block_given?
111
- alter_column( column ) do |val, record|
112
- yield val, record
113
- end
114
- end
51
+ @records.map{ |r| r.data << "" }
52
+
53
+ set_column_mapping
54
+ alter_column( col, &block ) if block_given?
115
55
  end
116
56
 
117
- def drop_column( column )
118
- raise_on_missing_columns( column )
119
-
120
- @columns.delete( column )
121
57
 
122
- key = column.to_s
58
+ def drop_column( col )
59
+ index = index_of_column( col )
60
+ @columns.delete( col.to_sym )
123
61
 
124
- @records.map{ |r|
125
- r.csv_data.delete( key )
126
- }
62
+ @records.map(&:data).map do |dat|
63
+ dat.delete_at( index )
64
+ end
127
65
 
128
- update_data_accessor_module
66
+ remove_column_from_mapping( col )
129
67
  end
130
68
 
131
- def rename_column( column, new_name )
132
- @columns[@columns.index(column)] = new_name
133
- rename_index_column( column, new_name ) if @index_columns.include?( column )
134
- update_data_accessor_module
69
+ def rename_column( old_column_name, new_name )
70
+ raise_on_missing_columns( old_column_name )
71
+ raise_on_forbidden_column_names( new_name )
72
+
73
+ # - rename the column itself
74
+ # - rename the column in the index
75
+ # - rename the column in the column mapping
76
+ column_index = index_of_column( old_column_name )
77
+
78
+ @columns[column_index] = new_name
79
+
80
+ if indexing_enabled? && has_index_column?( old_column_name )
81
+ rename_index_column( column, new_name )
82
+ end
83
+
84
+ rename_column_mapping_key( old_column_name, new_name )
135
85
  end
136
-
86
+
137
87
  def set_column_type( column, type, blank = :undefined )
138
- alter_column( column, blank, &COLUMN_TYPES[type] )
88
+ type = ColumnType::COLUMN_TYPE_LOOKUP[type]
89
+
90
+ alter_column( column, blank ) do |cell|
91
+ begin
92
+ type.convert( cell )
93
+ rescue StandardError => e
94
+ "ERROR: #{cell} (#{e.message})"
95
+ end
96
+ end
139
97
  end
140
98
 
141
99
 
@@ -167,11 +125,41 @@ module CsvMadness
167
125
 
168
126
  alias :concatenate :concat_columns
169
127
 
128
+ def index_of_column( col )
129
+ case col
130
+ when Integer
131
+ col
132
+ when Symbol, String
133
+ raise_on_missing_columns( col )
134
+ @column_mapping[ col.to_sym ]
135
+ end
136
+ end
137
+
170
138
  protected
139
+ def rename_column_mapping_key( old_column_name, new_name )
140
+ @column_mapping[new_name] = @column_mapping.delete( old_column_name )
141
+ end
142
+
143
+ def remove_column_from_mapping( col )
144
+ index = @column_mapping.delete( col.to_sym )
145
+
146
+ @column_mapping.each do |_col, i|
147
+ @column_mapping[_col] = i - 1 if i > index
148
+ end
149
+ end
150
+
151
+
152
+
171
153
  def raise_on_missing_columns( *cols )
172
154
  missing_columns = cols.flatten.reject{ |col| self.has_column?( col ) }
173
155
 
174
- raise MissingColumnError.new( "#{caller[0]}: required_column(s) :#{missing_columns.inspect} not found." ) unless missing_columns.fwf_blank?
156
+ raise MissingColumnError.new( "#{caller[0]}: requested column(s) :#{missing_columns.inspect} not found." ) unless missing_columns.fwf_blank?
157
+ end
158
+
159
+ def raise_on_columns_present( *cols )
160
+ present_columns = cols.flatten.select{ |col| self.has_column?( col ) }
161
+
162
+ raise ColumnExistsError.new( "#{caller[0]}: requested column(s) already present: #{present_columns.inspect}" ) unless present_columns.fwf_blank?
175
163
  end
176
164
 
177
165
  def forbidden_column_name?( col )
@@ -20,7 +20,9 @@ module CsvMadness
20
20
  end
21
21
 
22
22
  if file
23
- self.write_to_file( self.spreadsheet_file, @opts || {} )
23
+ opts = (@opts || {}).clone
24
+ opts.delete(:header)
25
+ self.write_to_file( file, opts)
24
26
  else
25
27
  raise "CsvMadness.save(:timestamp) - Spreadsheet must be specified by spreadsheet_file"
26
28
  end
@@ -29,12 +31,13 @@ module CsvMadness
29
31
 
30
32
  def reload_spreadsheet( opts = @opts )
31
33
  load_csv if @spreadsheet_file
32
- set_initial_columns( opts[:columns] )
33
- create_record_class
34
- package
34
+ set_columns( opts[:columns] )
35
+ package_each_csv_row
35
36
 
36
- set_index_columns( opts[:index] )
37
- reindex
37
+ if indexing_enabled?
38
+ set_index_columns( opts[:index] )
39
+ reindex
40
+ end
38
41
  end
39
42
  end
40
43
  end