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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 67d5895445a9fe397df95275260491ed6d9f6ce5
4
- data.tar.gz: 542a1442466867afa33cf0ef883a32443777cfd5
2
+ SHA256:
3
+ metadata.gz: 174fc0ba77e52795b1763e2ff29eae5e5c3cfa3bcd181973aaba886a54a79eeb
4
+ data.tar.gz: 7676780e13435e10bf35b7c34c849484ea0efe0a86d7889ff2a4697f312a99c6
5
5
  SHA512:
6
- metadata.gz: 1035dac76f5ec71d97015a5d9c2874074c28ad4b0ff7994ab565735fd6d49e539e4292a42ee15f44d8d2de2fb356e81a06f61685019fa3ad250d12ab9c160ba8
7
- data.tar.gz: 93c748026adc2fa907f3e08825c0fef8b41fed5b2945e3066aff70978a86ed4c9d47df3a98abc30e25b2fb44a597cddb91efbb519699d2a9e3b4c580b4ef1100
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
@@ -1,3 +1,5 @@
1
+ require 'csv_party/configuration'
2
+
1
3
  module CSVParty
2
4
  module DSL
3
5
  def column(column, options = {}, &block)
@@ -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.new(prepare_numeric_value(value))
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, config, runner)
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
@@ -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
- row = csv.shift
59
- break unless row
60
- import_row!(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, config, self)
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.rc4
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.0'
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
- rubyforge_project:
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