csv_party 1.0.0.rc4 → 1.0.0.rc5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/ROADMAP.md +84 -0
- data/lib/csv_party/data_preparer.rb +4 -1
- data/lib/csv_party/dsl.rb +2 -0
- data/lib/csv_party/parsers.rb +3 -1
- data/lib/csv_party/row.rb +5 -4
- data/lib/csv_party/runner.rb +28 -61
- data/lib/csv_party/validations.rb +41 -0
- data/lib/csv_party.rb +0 -6
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 174fc0ba77e52795b1763e2ff29eae5e5c3cfa3bcd181973aaba886a54a79eeb
|
4
|
+
data.tar.gz: 7676780e13435e10bf35b7c34c849484ea0efe0a86d7889ff2a4697f312a99c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab4bc476717516d1fa2a9d674f47e60c95ef0b24541cb7cf6cf76948ca5b9ce9dad96b9b89d10edfd99f338d4a04b612fd241c38d37d746d049f6eedb8868120
|
7
|
+
data.tar.gz: 4041355ba68669a3cf3e6275bf099da83d3d92d759bfc328269080a41dcbee02fa9a77b9c95c3549eaf5330cfc9fbbdcfdea05f83d320d2f29b55e89553d3fea
|
data/ROADMAP.md
CHANGED
@@ -8,9 +8,13 @@ Roadmap
|
|
8
8
|
- [1.5 Runtime Configuration](#15-runtime-configuration)
|
9
9
|
- [1.6 CSV Parse Error Handling](#16-csv-parse-error-handling)
|
10
10
|
- [Someday Features](#someday-features)
|
11
|
+
- [Parse Row Access](#parse-row-access)
|
12
|
+
- [Deferred Parsing](#deferred-parsing)
|
13
|
+
- [Columns Macro](#columns-macro)
|
11
14
|
- [Column Numbers](#column-numbers)
|
12
15
|
- [Multi-column Parsing](#multi-column-parsing)
|
13
16
|
- [Parse Dependencies](#parse-dependencies)
|
17
|
+
- [Rails Generator](#rails-generator0
|
14
18
|
|
15
19
|
#### 1.1 Early Return While Parsing
|
16
20
|
|
@@ -230,6 +234,68 @@ error handling API for non-parse errors. So:
|
|
230
234
|
|
231
235
|
## Someday Features
|
232
236
|
|
237
|
+
#### Parse Row Access
|
238
|
+
|
239
|
+
This feature would allow access to the `CSV::Row` object when parsing a column.
|
240
|
+
It could work something like this:
|
241
|
+
|
242
|
+
column product do |value, row|
|
243
|
+
Product.find_by(name: row['Product'])
|
244
|
+
end
|
245
|
+
|
246
|
+
In theory, the CSVParty API would cover all use cases where somebody would need
|
247
|
+
to access the raw row data, but perhaps not. Sometimes it's nice to be able to
|
248
|
+
cut through the stuff in your way and just get at the raw internals.
|
249
|
+
|
250
|
+
Additionally, perhaps deferred parsing would allow access to parsed row values,
|
251
|
+
which would possibly enable some of the features below, Multi Column Parsing and
|
252
|
+
Parse Dependencies, without requiring additional code. That might look like:
|
253
|
+
|
254
|
+
column product do |value, row|
|
255
|
+
Product.find_by(name: row.unparsed.row)
|
256
|
+
end
|
257
|
+
|
258
|
+
#### Deferred Parsing
|
259
|
+
|
260
|
+
Currently, CSVParty parses all columns before the row import logic is executed.
|
261
|
+
There are situations where parsing columns is expensive and you may want to
|
262
|
+
defer parsing columns until and unless you actually need them for that a given
|
263
|
+
row. For example, say you have an import where you are either:
|
264
|
+
|
265
|
+
1. Updating a value on existing records, or
|
266
|
+
2. Setting a value on and creating new records
|
267
|
+
|
268
|
+
And when you create new records, you have to also set a bunch of other values
|
269
|
+
and some of those values require database queries.
|
270
|
+
|
271
|
+
In a situation like this, you would want to defer all of the database queries
|
272
|
+
that need to run when creating a new record so that they aren't done in cases
|
273
|
+
where you are updating an existing record.
|
274
|
+
|
275
|
+
#### Columns Macro
|
276
|
+
|
277
|
+
This feature would allow you to declare multiple columns in a single line. So,
|
278
|
+
rather than:
|
279
|
+
|
280
|
+
column :product
|
281
|
+
column :price
|
282
|
+
column :color
|
283
|
+
|
284
|
+
You could do:
|
285
|
+
|
286
|
+
columns :product, :price, :color
|
287
|
+
|
288
|
+
This is probably most useful when there are a bunch of columns that should all
|
289
|
+
be parsed as text. Though it might make sense to allow specifying parsers and
|
290
|
+
other options:
|
291
|
+
|
292
|
+
columns product: { as: :raw }, price: { as: :decimal }
|
293
|
+
|
294
|
+
It should probably also be possible to combine `columns` and `column` macros:
|
295
|
+
|
296
|
+
columns :product, :price, :color
|
297
|
+
column :size
|
298
|
+
|
233
299
|
#### Column Numbers
|
234
300
|
|
235
301
|
CSVParty is entirely oriented around a CSV file having a header. This is not
|
@@ -269,3 +335,21 @@ that might look like:
|
|
269
335
|
customer.orders.find(order_id)
|
270
336
|
end
|
271
337
|
end
|
338
|
+
|
339
|
+
#### Rails Generator
|
340
|
+
|
341
|
+
This feature would add a generator for Rails which creates an importer file. So
|
342
|
+
doing:
|
343
|
+
|
344
|
+
rails generate importer Product
|
345
|
+
|
346
|
+
Would generate the following file at `app/importers/product_importer.rb`:
|
347
|
+
|
348
|
+
class ProductImporter
|
349
|
+
include CSVParty
|
350
|
+
|
351
|
+
import do |row|
|
352
|
+
# import logic goes here
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'csv_party/errors'
|
3
|
+
|
1
4
|
module CSVParty
|
2
5
|
class DataPreparer
|
3
6
|
def initialize(options)
|
@@ -15,7 +18,7 @@ module CSVParty
|
|
15
18
|
@options[:content]
|
16
19
|
end
|
17
20
|
options = extract_csv_options.merge(headers: true)
|
18
|
-
CSV.new(data, options)
|
21
|
+
CSV.new(data, **options)
|
19
22
|
end
|
20
23
|
|
21
24
|
private
|
data/lib/csv_party/dsl.rb
CHANGED
data/lib/csv_party/parsers.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
1
3
|
module CSVParty
|
2
4
|
module Parsers
|
3
5
|
def parse_raw(value)
|
@@ -25,7 +27,7 @@ module CSVParty
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def parse_decimal(value)
|
28
|
-
BigDecimal
|
30
|
+
BigDecimal(prepare_numeric_value(value))
|
29
31
|
end
|
30
32
|
|
31
33
|
def parse_date(value, format = nil)
|
data/lib/csv_party/row.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
1
3
|
module CSVParty
|
2
4
|
class Row
|
3
5
|
attr_accessor :row_number, :csv_string, :unparsed
|
4
6
|
|
5
|
-
def initialize(csv_row,
|
7
|
+
def initialize(csv_row, runner)
|
6
8
|
@csv_row = csv_row
|
7
|
-
@config = config
|
8
9
|
@runner = runner
|
9
10
|
@attributes = OpenStruct.new
|
10
11
|
parse_row!(csv_row)
|
@@ -16,7 +17,7 @@ module CSVParty
|
|
16
17
|
self.csv_string = csv_row.to_csv
|
17
18
|
self.unparsed = extract_unparsed_values(csv_row)
|
18
19
|
|
19
|
-
@config.columns.each do |column, options|
|
20
|
+
@runner.config.columns.each do |column, options|
|
20
21
|
header = options[:header]
|
21
22
|
value = csv_row[header]
|
22
23
|
@attributes[column] = parse_value(value, options)
|
@@ -25,7 +26,7 @@ module CSVParty
|
|
25
26
|
|
26
27
|
def extract_unparsed_values(csv_row)
|
27
28
|
unparsed_row = OpenStruct.new
|
28
|
-
config.columns.each do |column, options|
|
29
|
+
@runner.config.columns.each do |column, options|
|
29
30
|
header = options[:header]
|
30
31
|
unparsed_row[column] = csv_row[header]
|
31
32
|
end
|
data/lib/csv_party/runner.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'csv_party/parsers'
|
2
|
+
require 'csv_party/validations'
|
3
|
+
require 'csv_party/errors'
|
4
|
+
require 'csv_party/row'
|
2
5
|
|
3
6
|
module CSVParty
|
4
7
|
class Runner
|
5
8
|
include Parsers
|
9
|
+
include Validations
|
6
10
|
|
7
11
|
attr_accessor :csv, :config, :importer
|
8
12
|
|
@@ -52,24 +56,23 @@ module CSVParty
|
|
52
56
|
csv.rewind
|
53
57
|
end
|
54
58
|
|
59
|
+
def initialize_regex_headers!
|
60
|
+
config.columns_with_regex_headers.each do |name, options|
|
61
|
+
found_header = @_headers.find do |header|
|
62
|
+
options[:header].match(header)
|
63
|
+
end
|
64
|
+
options[:header] = found_header || name.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
55
68
|
def import_rows!
|
56
69
|
loop do
|
57
70
|
begin
|
58
|
-
|
59
|
-
break unless
|
60
|
-
import_row!(
|
61
|
-
rescue NextRowError
|
62
|
-
next
|
63
|
-
rescue SkippedRowError => error
|
64
|
-
handle_skipped_row(error)
|
65
|
-
rescue AbortedRowError => error
|
66
|
-
handle_aborted_row(error)
|
67
|
-
rescue AbortedImportError
|
68
|
-
raise
|
71
|
+
csv_row = csv.shift
|
72
|
+
break unless csv_row
|
73
|
+
import_row!(csv_row)
|
69
74
|
rescue CSV::MalformedCSVError
|
70
75
|
raise
|
71
|
-
rescue StandardError => error
|
72
|
-
handle_error(error, @_current_row_number, row.to_csv)
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
@@ -78,9 +81,20 @@ module CSVParty
|
|
78
81
|
|
79
82
|
def import_row!(csv_row)
|
80
83
|
@_current_row_number += 1
|
81
|
-
@_current_parsed_row = Row.new(csv_row,
|
84
|
+
@_current_parsed_row = Row.new(csv_row, self)
|
82
85
|
@_current_parsed_row.row_number = @_current_row_number
|
86
|
+
|
83
87
|
instance_exec(@_current_parsed_row, &config.row_importer)
|
88
|
+
rescue NextRowError
|
89
|
+
return
|
90
|
+
rescue SkippedRowError => error
|
91
|
+
handle_skipped_row(error)
|
92
|
+
rescue AbortedRowError => error
|
93
|
+
handle_aborted_row(error)
|
94
|
+
rescue AbortedImportError
|
95
|
+
raise
|
96
|
+
rescue StandardError => error
|
97
|
+
handle_error(error, @_current_row_number, csv_row.to_csv)
|
84
98
|
end
|
85
99
|
|
86
100
|
def next_row!
|
@@ -138,38 +152,6 @@ module CSVParty
|
|
138
152
|
.new(error, line_number, csv_string)
|
139
153
|
end
|
140
154
|
|
141
|
-
def raise_unless_row_processor_is_defined!
|
142
|
-
return if config.row_importer
|
143
|
-
|
144
|
-
raise UndefinedRowProcessorError.new
|
145
|
-
end
|
146
|
-
|
147
|
-
def raise_unless_rows_have_been_imported!
|
148
|
-
return if @_rows_have_been_imported
|
149
|
-
|
150
|
-
raise UnimportedRowsError.new
|
151
|
-
end
|
152
|
-
|
153
|
-
def raise_unless_all_dependencies_are_present!
|
154
|
-
config.dependencies.each do |dependency|
|
155
|
-
next unless importer.send(dependency).nil?
|
156
|
-
|
157
|
-
raise MissingDependencyError.new(self, dependency)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# This error has to be raised at runtime because, when the class body
|
162
|
-
# is being executed, the parser methods won't be available unless
|
163
|
-
# they are defined above the column definitions in the class body
|
164
|
-
def raise_unless_all_named_parsers_exist!
|
165
|
-
config.columns_with_named_parsers.each do |name, options|
|
166
|
-
parser = options[:parser]
|
167
|
-
next if named_parsers.include? parser
|
168
|
-
|
169
|
-
raise UnknownParserError.new(name, parser, named_parsers)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
155
|
def named_parsers
|
174
156
|
(methods +
|
175
157
|
private_methods +
|
@@ -177,21 +159,6 @@ module CSVParty
|
|
177
159
|
importer.private_methods).grep(/^parse_/)
|
178
160
|
end
|
179
161
|
|
180
|
-
def raise_unless_csv_has_all_columns!
|
181
|
-
return if missing_columns.empty?
|
182
|
-
|
183
|
-
raise MissingColumnError.new(present_columns, missing_columns)
|
184
|
-
end
|
185
|
-
|
186
|
-
def initialize_regex_headers!
|
187
|
-
config.columns_with_regex_headers.each do |name, options|
|
188
|
-
found_header = @_headers.find do |header|
|
189
|
-
options[:header].match(header)
|
190
|
-
end
|
191
|
-
options[:header] = found_header || name.to_s
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
162
|
def respond_to_missing?(method, _include_private)
|
196
163
|
importer.respond_to?(method, true)
|
197
164
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CSVParty
|
2
|
+
module Validations
|
3
|
+
def raise_unless_row_processor_is_defined!
|
4
|
+
return if config.row_importer
|
5
|
+
|
6
|
+
raise UndefinedRowProcessorError.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def raise_unless_rows_have_been_imported!
|
10
|
+
return if @_rows_have_been_imported
|
11
|
+
|
12
|
+
raise UnimportedRowsError.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def raise_unless_all_dependencies_are_present!
|
16
|
+
config.dependencies.each do |dependency|
|
17
|
+
next unless importer.send(dependency).nil?
|
18
|
+
|
19
|
+
raise MissingDependencyError.new(self, dependency)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This error has to be raised at runtime because, when the class body
|
24
|
+
# is being executed, the parser methods won't be available unless
|
25
|
+
# they are defined above the column definitions in the class body
|
26
|
+
def raise_unless_all_named_parsers_exist!
|
27
|
+
config.columns_with_named_parsers.each do |name, options|
|
28
|
+
parser = options[:parser]
|
29
|
+
next if named_parsers.include? parser
|
30
|
+
|
31
|
+
raise UnknownParserError.new(name, parser, named_parsers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def raise_unless_csv_has_all_columns!
|
36
|
+
return if missing_columns.empty?
|
37
|
+
|
38
|
+
raise MissingColumnError.new(present_columns, missing_columns)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/csv_party.rb
CHANGED
@@ -1,11 +1,5 @@
|
|
1
|
-
require 'bigdecimal'
|
2
|
-
require 'csv'
|
3
|
-
require 'ostruct'
|
4
|
-
require 'csv_party/configuration'
|
5
1
|
require 'csv_party/dsl'
|
6
2
|
require 'csv_party/data_preparer'
|
7
|
-
require 'csv_party/errors'
|
8
|
-
require 'csv_party/row'
|
9
3
|
require 'csv_party/runner'
|
10
4
|
|
11
5
|
module CSVParty
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_party
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rico Jones
|
@@ -28,6 +28,7 @@ files:
|
|
28
28
|
- lib/csv_party/row.rb
|
29
29
|
- lib/csv_party/runner.rb
|
30
30
|
- lib/csv_party/testing.rb
|
31
|
+
- lib/csv_party/validations.rb
|
31
32
|
homepage: https://github.com/toasterlovin/csv_party
|
32
33
|
licenses:
|
33
34
|
- MIT
|
@@ -40,15 +41,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
41
|
requirements:
|
41
42
|
- - ">="
|
42
43
|
- !ruby/object:Gem::Version
|
43
|
-
version: '2.
|
44
|
+
version: '2.6'
|
44
45
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
46
|
requirements:
|
46
47
|
- - ">"
|
47
48
|
- !ruby/object:Gem::Version
|
48
49
|
version: 1.3.1
|
49
50
|
requirements: []
|
50
|
-
|
51
|
-
rubygems_version: 2.6.11
|
51
|
+
rubygems_version: 3.0.3.1
|
52
52
|
signing_key:
|
53
53
|
specification_version: 4
|
54
54
|
summary: CSV Party
|