iron-import 0.8.1 → 0.8.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af5ddfc35604cb25d28377798c3ea609ca7e575e
4
- data.tar.gz: 76a6ed3cc099d49e6a5fa888b3a37dff26e2bee8
3
+ metadata.gz: ce84c9899f23abe7fd1f3ed06dc3f50e8986a585
4
+ data.tar.gz: e1cd18c73aa8663bd0b98305dd18b0ddaee9f75e
5
5
  SHA512:
6
- metadata.gz: d8d0f3a7cd1a6a2503df394b94dd74630d1226e4c1b0ed43fe9d76d0f264df358641ce7be2d51f30b7c0d870118af109f7e183de37e7a8d3a0c4d3aea8cd68b7
7
- data.tar.gz: 3d6d629a0c09ce84b526025d945b55d0f4c6b8e250b3053b43ddb5fdba91e62e7c13a623f66c65a2e39700dd632464471309a6c3357b04c5e7e45c4b757a69ba
6
+ metadata.gz: ca04f44c1434825045fb27b1d2aa0a2b16b70174dc43bf5f2aa89212331dc06e06ef41d1e4f0070971325f9014d0b6ed4e17a20cf47cb137c2312334255690ee
7
+ data.tar.gz: 824b6842a4a2c60c1d1ee8737b490fe96e8029e3f8042fa548b85927d8735d2c25c551b73fbc769e407294cdbcc663ec888477746833cb8296c9ffa5ffcaf928
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ == 0.8.2 / 2017-08-01
2
+ * Add Importer#on_success to enable conditional processing once an import has been successfully completed
3
+ * Pre-parse values with Column#type set when using Column#parse (instead of ignoring it)
4
+ * Make importer scope available in Importer#process and the block form of Importer#import
5
+ * Add backtrace info to error logging for exceptions to help during debugging
6
+
1
7
  == 0.8.1 / 2017-07-18
2
8
  * Do not include optional headers in #missing_headers
3
9
  * Improve string parsing to strip trailing '.0' from incoming float values
data/Version.txt CHANGED
@@ -1 +1 @@
1
- 0.8.1
1
+ 0.8.2
@@ -29,7 +29,9 @@ class Importer
29
29
  # # that different source types may give you different raw values for what
30
30
  # # seems like the "same" source value, for example an Excel source file
31
31
  # # will give you a float value for all numeric types, even "integers", while
32
- # # CSV and HTML values are always strings.
32
+ # # CSV and HTML values are always strings. By default, will take the raw
33
+ # # value of the row, but if used with #type, you can have the pre-processing
34
+ # # of the type as your input.
33
35
  # parse do |raw_value|
34
36
  # val = raw_value.to_i + 1000
35
37
  # # NOTE: we're in a block, so don't do this:
@@ -125,7 +127,7 @@ class Importer
125
127
  @virtual = options_hash.delete(:virtual) { false }
126
128
 
127
129
  # Return it as a string, by default
128
- @type = options_hash.delete(:type) { :string }
130
+ @type = options_hash.delete(:type)
129
131
 
130
132
  # Position can be explicitly set
131
133
  @position = options_hash.delete(:position)
@@ -184,6 +186,24 @@ class Importer
184
186
  end
185
187
  end
186
188
 
189
+ # Override normal dsl_accessor behavior to return our default type
190
+ # which will be :raw if a #parse handler has been set, else :string
191
+ alias_method :internal_type, :type
192
+ def type(*args)
193
+ if args.count > 0
194
+ internal_type(*args)
195
+ else
196
+ if @type
197
+ # Explicitly set type
198
+ @type
199
+ else
200
+ # Our default is generally :string, but if we have a parser,
201
+ # default to the :raw value
202
+ parses? ? :raw : :string
203
+ end
204
+ end
205
+ end
206
+
187
207
  # Applies any custom parser defined to process the given value, capturing
188
208
  # errors as needed
189
209
  def parse_value(row, raw_val)
@@ -42,7 +42,7 @@ class Importer
42
42
 
43
43
  rescue Exception => e
44
44
  # Catch any exceptions thrown and note them with helpful stacktrace info for debugging custom readers
45
- add_error("Error in custom reader: #{e} @ #{e.backtrace.first}")
45
+ add_exception(e)
46
46
  end
47
47
 
48
48
  end
@@ -219,10 +219,16 @@ class Importer
219
219
  return nil if val.nil? || val.to_s.strip == ''
220
220
 
221
221
  case type
222
+ when :raw then
223
+ val
224
+
222
225
  when :string then
223
226
  if val.is_a?(Float)
224
- val.to_s.strip.gsub(/\.0+$/, '')
227
+ # Sometimes float values come in for "integer" columns from Excel,
228
+ # so if the user asks for a string, strip off that ".0" if present
229
+ val.to_s.gsub(/\.0+$/, '')
225
230
  else
231
+ # Strip whitespace and we're good to go
226
232
  val.to_s.strip
227
233
  end
228
234
 
@@ -248,7 +254,7 @@ class Importer
248
254
  if val.class < Numeric
249
255
  val.to_f
250
256
  else
251
- # Convert to string, strip off trailing decimal zeros
257
+ # Clean up then verify it matches a valid float format & convert
252
258
  val = val.to_s.strip
253
259
  if val.match(/\A-?[0-9]+(?:\.[0-9]+)?\z/)
254
260
  val.to_f
@@ -287,6 +293,10 @@ class Importer
287
293
  @importer.add_error(*args)
288
294
  end
289
295
 
296
+ def add_exception(*args)
297
+ @importer.add_exception(*args)
298
+ end
299
+
290
300
  end
291
301
 
292
302
  end
@@ -21,7 +21,7 @@ class Importer
21
21
  yield
22
22
  rescue RuntimeError => e
23
23
  # Old-style way of registering errors was to just raise 'foo'
24
- importer.add_error(e.to_s)
24
+ importer.add_exception(e)
25
25
  end
26
26
  had_error = @error_occurred
27
27
 
@@ -25,7 +25,7 @@ class Importer
25
25
  false
26
26
  end
27
27
  rescue Exception => e
28
- add_error("Error reading #{source}: #{e}")
28
+ add_exception(e)
29
29
  false
30
30
  end
31
31
 
@@ -47,7 +47,7 @@ class Importer
47
47
 
48
48
  rescue Exception => e
49
49
  # Not sure why we'd get here, but we strive for error-freedom here, yessir.
50
- @importer.add_error("Error loading Excel data: #{e}")
50
+ add_exception(e)
51
51
  end
52
52
 
53
53
  # When true, the given sheet name or zero-based index
@@ -27,7 +27,7 @@ class Importer
27
27
  end
28
28
 
29
29
  rescue Exception => e
30
- add_error("Error reading HTML source #{source}: #{e}")
30
+ add_exception(e)
31
31
  false
32
32
  end
33
33
 
@@ -65,7 +65,7 @@ class Importer
65
65
 
66
66
  rescue Exception => e
67
67
  # Not sure why we'd get here, but we strive for error-freedom here, yessir.
68
- add_error("Error loading tables #{scopes.list_join(', ')}: #{e}")
68
+ add_exception(e)
69
69
  end
70
70
 
71
71
  end
@@ -76,6 +76,10 @@
76
76
  # end.on_error do
77
77
  # # If we have any errors, do something
78
78
  # raise error_summary
79
+ #
80
+ # end.on_success do
81
+ # # No errors, do this
82
+ # puts "Imported successfully!"
79
83
  # end
80
84
  #
81
85
  class Importer
@@ -418,12 +422,12 @@ class Importer
418
422
  # will not be present. If you want to register an error, simply
419
423
  # raise "some text" or call #add_error and it will be added to the importer's
420
424
  # error list for display to the user, logging, or whatever.
421
- def process
425
+ def process(&block)
422
426
  @data.rows.each do |row|
423
427
  begin
424
- yield row
428
+ DslProxy.exec(self, row, &block)
425
429
  rescue Exception => e
426
- add_error(e.to_s, :row => row)
430
+ add_exception(e, :row => row)
427
431
  end
428
432
  end
429
433
  end
@@ -448,6 +452,24 @@ class Importer
448
452
  self
449
453
  end
450
454
 
455
+ # Call with a block to process any post-import tasks. Block will only execute
456
+ # if an import has been run with no errors.
457
+ #
458
+ # Your block can access the #rows to do whatever
459
+ # logging, reporting etc. is desired.
460
+ def on_success(&block)
461
+ raise 'Invalid block passed to Importer#on_succes: block may accept 0 or 1arguments' if block.arity > 1
462
+
463
+ if @data.rows.any? && !has_errors?
464
+ case block.arity
465
+ when 0 then DslProxy.exec(self, &block)
466
+ when 1 then DslProxy.exec(self, @data.rows, &block)
467
+ end
468
+ end
469
+
470
+ self
471
+ end
472
+
451
473
  # Call with a block accepting an array of Column objects and returning
452
474
  # true if the columns in the array should constitute a valid header row. Intended
453
475
  # for use with optional columns to define multiple supported column sets, or
@@ -557,12 +579,11 @@ class Importer
557
579
  if col.present? && !col.virtual?
558
580
  index = col.data.index
559
581
  raw_val = raw_data[index]
582
+ # Otherwise use our standard parser
583
+ val = @reader.parse_value(raw_val, col.type)
560
584
  if col.parses?
561
585
  # Use custom parser if this row has one
562
- val = col.parse_value(row, raw_val)
563
- else
564
- # Otherwise use our standard parser
565
- val = @reader.parse_value(raw_val, col.type)
586
+ val = col.parse_value(row, val)
566
587
  end
567
588
  values[col.key] = val
568
589
  end
@@ -631,6 +652,12 @@ class Importer
631
652
  @data.errors << Error.new(msg, context)
632
653
  end
633
654
 
655
+ def add_exception(ex, context = {})
656
+ line = ex.backtrace.first.extract(/[a-z0-9_\-\.]+\:[0-9]+\:/i)
657
+ msg = [line, ex.to_s].list_join(' ')
658
+ @data.errors << Error.new(msg, context)
659
+ end
660
+
634
661
  # Returns a human-readable summary of the errors present on the importer, or
635
662
  # nil if no errors are present
636
663
  def error_summary
@@ -647,10 +674,11 @@ class Importer
647
674
 
648
675
  # Build summary & return
649
676
  list.values.collect do |errs|
650
- summary = errs.first.summary
677
+ err = errs.first
651
678
  if errs.count == 1
652
- summary
679
+ err.summary
653
680
  else
681
+ summary = [err.column.to_s, err.text].list_join(': ')
654
682
  errs.count.to_s + ' x ' + summary
655
683
  end
656
684
  end.list_join(', ')
@@ -23,21 +23,6 @@ describe Importer do
23
23
  importer.scopes.should == { :xls => [1, 'Sheet 2'], :html => ['table.funny'] }
24
24
  end
25
25
 
26
- it 'should calculate virtual columns' do
27
- importer = Importer.build do
28
- column :num, :type => :int
29
- virtual_column :summary do
30
- calculate do |row|
31
- "Value = #{row[:num]}"
32
- end
33
- end
34
- end
35
-
36
- importer.import_string("num\n1\n2")
37
- importer.error_summary.should be_nil
38
- importer.column(:summary).to_a.should == ['Value = 1', 'Value = 2']
39
- end
40
-
41
26
  it 'should find headers automatically' do
42
27
  # Define a few sample columns
43
28
  importer = Importer.new
@@ -88,6 +73,37 @@ describe Importer do
88
73
  importer.find_header(rows).should be_true
89
74
  importer.missing_headers.should be_empty
90
75
  end
76
+
77
+ it 'should calculate virtual columns' do
78
+ importer = Importer.build do
79
+ column :num, :type => :int
80
+ virtual_column :summary do
81
+ calculate do |row|
82
+ "Value = #{row[:num]}"
83
+ end
84
+ end
85
+ end
86
+
87
+ importer.import_string("num\n1\n2")
88
+ importer.error_summary.should be_nil
89
+ importer.column(:summary).to_a.should == ['Value = 1', 'Value = 2']
90
+ end
91
+
92
+ it 'should honor type before applying custom parsers' do
93
+ importer = Importer.new
94
+ importer.column(:alpha) do
95
+ parse do |raw|
96
+ raw
97
+ end
98
+ end
99
+
100
+ importer.import_string("alpha\n1.0\n1.5")
101
+ importer.to_a.should == [{:alpha => '1.0'}, {:alpha => '1.5'}]
102
+
103
+ importer.column(:alpha).type :float
104
+ importer.import_string("alpha\n1.0\n1.5")
105
+ importer.to_a.should == [{:alpha => 1.0}, {:alpha => 1.5}]
106
+ end
91
107
 
92
108
  it 'should support row-based validation' do
93
109
  importer = Importer.build do
@@ -153,6 +169,25 @@ describe Importer do
153
169
  was_run.should be_true
154
170
  end
155
171
 
172
+ it 'should run conditional code when successful' do
173
+ importer = Importer.build do
174
+ column :foo
175
+ end
176
+
177
+ was_run = false
178
+ importer.on_success do
179
+ was_run = true
180
+ end
181
+ was_run.should be_false
182
+
183
+ importer.import_string("foo\n1")
184
+ importer.has_errors?.should be_false
185
+ importer.on_success do
186
+ was_run = true
187
+ end
188
+ was_run.should be_true
189
+ end
190
+
156
191
  it 'should import a test csv file' do
157
192
  importer = Importer.build do
158
193
  column :number
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iron-import
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Morris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-18 00:00:00.000000000 Z
11
+ date: 2017-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: iron-extensions