gunwale 0.5.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 83044c55ce9d087739769f71c931c017cacd6d99b41bf60ee55015702c9204bb
4
+ data.tar.gz: aef66c8ba50fe24a8cf184d5c2d08a58e2d42af39f287aa3ca88c049316f55ca
5
+ SHA512:
6
+ metadata.gz: 9e091778396ddb89c50142472c4aa2b4bbfb0a81158f76db13b2a57ab30e4cc930279e7b8806c63f0b8e434ab6d5356a1994e0bb136e9b734332f95e31b26085
7
+ data.tar.gz: 7a223936945200c527840a0f472d23364360da51bbcf69fac890f1a49f733ecb6b38447498c8f99bc7f07a8ac0f9ec00dab7f73b9df7693eadc9d8ac1a060821
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format progress
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,63 @@
1
+ AllCops:
2
+ Include:
3
+ - "**/Rakefile"
4
+ Exclude:
5
+ - db/**/*
6
+ - gemfiles/**/*
7
+ - vendor/**/*
8
+ Lint/SpaceBeforeFirstArg:
9
+ Enabled: false
10
+ Lint/UnusedBlockArgument:
11
+ Enabled: false
12
+ Lint/UnusedMethodArgument:
13
+ Enabled: false
14
+ Metrics/AbcSize:
15
+ Enabled: false
16
+ Metrics/ClassLength:
17
+ Enabled: false
18
+ Metrics/CyclomaticComplexity:
19
+ Enabled: false
20
+ Metrics/LineLength:
21
+ Enabled: false
22
+ Metrics/MethodLength:
23
+ Enabled: false
24
+ Metrics/ModuleLength:
25
+ Enabled: false
26
+ Metrics/PerceivedComplexity:
27
+ Enabled: false
28
+ Metrics/BlockLength:
29
+ Enabled: false
30
+ Security/YAMLLoad:
31
+ Enabled: false
32
+ Style/AlignParameters:
33
+ Enabled: false
34
+ Style/ClassAndModuleChildren:
35
+ Enabled: false
36
+ Style/ClassVars:
37
+ Enabled: false
38
+ Style/Documentation:
39
+ Enabled: false
40
+ Style/DoubleNegation:
41
+ Enabled: false
42
+ Style/FileName:
43
+ Enabled: false
44
+ Style/GuardClause:
45
+ Enabled: false
46
+ Style/IndentHash:
47
+ Enabled: false
48
+ Style/NilComparison:
49
+ Enabled: false
50
+ Style/OpMethod:
51
+ Enabled: false
52
+ Style/RescueModifier:
53
+ Enabled: false
54
+ Style/SignalException:
55
+ Enabled: false
56
+ Style/SingleLineMethods:
57
+ Enabled: false
58
+ Style/SpaceAroundEqualsInParameterDefault:
59
+ Enabled: false
60
+ Style/StringLiterals:
61
+ EnforcedStyle: double_quotes
62
+ Performance/EndWith:
63
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ dist: trusty
2
+ sudo: false
3
+ language: ruby
4
+ rvm:
5
+ - 2.3.3
6
+ - 2.4.1
7
+ addons:
8
+ postgresql: '9.6'
9
+ before_install:
10
+ - gem update --system
11
+ - gem --version
12
+ - gem install bundler -v 1.14.6
13
+ before_script:
14
+ - bundle exec rake db:create
15
+ - bundle exec rake db:migrate
16
+ cache: bundler
17
+ gemfile:
18
+ - gemfiles/csv_import_1.1_and_0.18.gemfile
data/API.md ADDED
@@ -0,0 +1,387 @@
1
+ # RowBoat API
2
+
3
+ This is really more of a summary of what you can do with `RowBoat::Base` since you subclass it to do everything :)
4
+
5
+ ## Contents
6
+
7
+ - [Basic Usage](#basic-usage)
8
+ - [`.import`](#import)
9
+ - [`initialize`](#initialize)
10
+ - [`import`](#import-1)
11
+ - [`import_into`](#import_into)
12
+ - [`csv_source`](#csv_source)
13
+ - [`column_mapping`](#column_mapping)
14
+ - [`preprocess_row`](#preprocess_row)
15
+ - [`preprocess_rows`](#preprocess_rows)
16
+ - [`options`](#options)
17
+ - [`handle_failed_row`](#handle_failed_row)
18
+ - [`handle_failed_rows`](#handle_failed_rows)
19
+ - [`value_converters`](#value_converters)
20
+ - [`rollback_transaction?`](#rollback_transaction)
21
+
22
+ ## Basic Usage
23
+
24
+ Just subclass `RowBoat::Base` and define the [`import_into`](#import_into) and [`column_mapping`](#column_mapping) methods to get started (They're the only methods that you're required to implement).
25
+
26
+ ```ruby
27
+ class ImportProduct < RowBoat::Base
28
+ def import_into
29
+ Product
30
+ end
31
+
32
+ def column_mapping
33
+ {
34
+ downcased_csv_column_header: :model_attribute_name,
35
+ another_downcased_csv_column_header: :another_model_attribute_name
36
+ }
37
+ end
38
+ end
39
+ ```
40
+
41
+ ## `.import`
42
+
43
+ ### Description
44
+ Imports database records form the given CSV-like object. The CSV-like object can be anything that can be passed to [`SmarterCSV.process`](https://github.com/tilo/smarter_csv#documentation) (string paths to files, files, tempfiles, instances of StringIO, etc).
45
+
46
+ It returns a hash containing
47
+
48
+ - `:invalid_records` - an array of all records that failed to import since they were invalid. If you've configured the `:validate` option to be `false` it will be an empty array.
49
+ - `:total_inserts` - the total number of database inserts that were run.
50
+ - `:inserted_ids` - an array of all of the ids of records inserted into the database.
51
+ - `:skipped_rows` - every row skipped by returning `nil` from [`preprocess_row`](#preprocess_row).
52
+
53
+ If you want to pass additional information to help import CSVs, *don't override this method*. It just passes through to [`initialize`](#initialize) so override that :)
54
+
55
+ ### Example
56
+
57
+ #### Basic Use
58
+
59
+ ```ruby
60
+ class ImportProduct < RowBoat::Base
61
+ # required configuration omitted for brevity
62
+ end
63
+
64
+ ImportProduct.import("path/to/my.csv")
65
+ ```
66
+
67
+ #### Advanced Use
68
+
69
+ ```ruby
70
+ class ImportProduct < RowBoat::Base
71
+ # required configuration omitted for brevity
72
+ def intitialize(csv_source, my_options)
73
+ super(csv_source)
74
+ @my_options = my_options
75
+ end
76
+ end
77
+
78
+ ImportProduct.import("path/to/my.csv", foo: "bar")
79
+ ```
80
+
81
+ ## `initialize`
82
+
83
+ ### Description
84
+
85
+ Makes a new instance with the given CSV-like object. See [`.import`](#import) for more details around when and how to override this method.
86
+
87
+ ## `import`
88
+
89
+ ### Description
90
+
91
+ The instance method that actually parses and imports the CSV. Generally, you wouldn't call this directly and would instead call [`.import`](#import).
92
+
93
+ ## `import_into`
94
+
95
+ ### Description
96
+
97
+ It is required that you override this method to return whatever ActiveRecord class you want your CSV imported into.
98
+
99
+ ### Example
100
+
101
+ #### Basic Use
102
+
103
+ ```ruby
104
+ class ImportProduct < RowBoat::Base
105
+ # other required configuration omitted for brevity
106
+ def import_into
107
+ Product
108
+ end
109
+ end
110
+ ```
111
+
112
+ #### Advanced Use
113
+
114
+ ```ruby
115
+ class ImportProduct < RowBoat::Base
116
+ # other required configuration omitted for brevity
117
+ def import_into
118
+ if csv_source.is_a?(String) && csv_source.match(/category/i)
119
+ ProductCategory
120
+ else
121
+ Product
122
+ end
123
+ end
124
+ end
125
+
126
+ ImportProduct.import("path/to/category.csv")
127
+ ImportProduct.import("path/to/product.csv")
128
+ ```
129
+
130
+
131
+ ## `csv_source`
132
+
133
+ ### Description
134
+
135
+ Whatever you originally passed in as the CSV source.
136
+
137
+ ### Example
138
+
139
+ ```ruby
140
+ class ImportProduct < RowBoat::Base
141
+ # other required configuration omitted for brevity
142
+ def import_into
143
+ # `csv_source` is available in any of our instance methods
144
+ if csv_source.is_a?(String) && csv_source.match(/category/i)
145
+ ProductCategory
146
+ else
147
+ Product
148
+ end
149
+ end
150
+ end
151
+
152
+ ImportProduct.import("path/to/category.csv")
153
+ ImportProduct.import("path/to/product.csv")
154
+ ```
155
+
156
+ ## `column_mapping`
157
+
158
+ ### Description
159
+
160
+ It is required that you override this method with either a hash that maps columns in your CSV to their preferred names or an array of your preferred column names.
161
+
162
+ By default when using a hash
163
+ - CSV column names are downcased symbols of what they look like in the CSV.
164
+ - CSV columns that are not mapped are ignored when processing the CSV.
165
+
166
+ If you're familiar with [SmarterCSV](https://github.com/tilo/smarter_csv#documentation), this method essentially defines your `:key_mapping` with the `:remove_unmapped_keys` setting set to `true` when provided with a hash. When given an array it is the `:user_provided_headers` option.
167
+
168
+ You can change these defaults by overriding the [`options`](#options) method.
169
+
170
+ ### Example
171
+
172
+ ```ruby
173
+ class ImportProduct < RowBoat::Base
174
+ # other required configuration omitted for brevity
175
+ def column_mapping
176
+ {
177
+ prdct_nm: :name,
178
+ "price/cost_amnt": :price_in_cents
179
+ }
180
+ end
181
+ end
182
+
183
+ # or...
184
+
185
+ class ImportProduct < RowBoat::Base
186
+ # other required configuration omitted for brevity
187
+ def column_mapping
188
+ [:name, :price_in_cents]
189
+ end
190
+ end
191
+ ```
192
+
193
+ ## `preprocess_row`
194
+
195
+ ### Description
196
+
197
+ Implement this method if you need to do some work on the row before the record is inserted/updated.
198
+
199
+ If you return `nil` from this method, the row will be skipped in the import. You can access these rows in the return value from [`.import`](#import) (the `:skipped_rows` key).
200
+
201
+ You also have access to `row_number` here.
202
+
203
+ If the work you intend to do with the row only requires changing one attribute, it is recommended that you override [`value_converters`](#value_converters) instead of this.
204
+
205
+ ### Example
206
+
207
+ ```ruby
208
+ class ImportProduct < RowBoat::Base
209
+ # required configuration omitted for brevity
210
+ def preprocess_row(row)
211
+ { position: row_number }.merge(row)
212
+ end
213
+ # or...
214
+ def preprocess_row(row)
215
+ if row[:name] && row[:price]
216
+ row
217
+ else
218
+ nil
219
+ end
220
+ end
221
+ end
222
+ ```
223
+
224
+ ## `preprocess_rows`
225
+
226
+ ### Description
227
+
228
+ Override this method if you need to do something with a chunk of rows (the chunk size is determined by the `:chunk_size` option in the [`options`](#options) method).
229
+
230
+ If you need to filter particular rows, it's better to just implement [`preprocess_row`](#preprocess_row) and return `nil` for the rows you want to ignore.
231
+
232
+ ### Example
233
+
234
+ ```ruby
235
+ class ImportProduct < RowBoat::Base
236
+ # required configuration omitted for brevity
237
+ def preprocess_rows(rows)
238
+ if skip_batch?(rows)
239
+ super([])
240
+ else
241
+ super
242
+ end
243
+ end
244
+
245
+ def skip_batch?(rows)
246
+ # decide whether or not to skip the batch
247
+ end
248
+ end
249
+ ```
250
+
251
+ ## `options`
252
+
253
+ ### Description
254
+
255
+ Implement this to configure RowBoat, [SmarterCSV](https://github.com/tilo/smarter_csv), and [activerecord-import](https://github.com/zdennis/activerecord-import).
256
+
257
+ Except for `:wrap_in_transaction`, all options pass through to SmarterCSV and activerecord-import.
258
+
259
+ `:wrap_in_transaction` simply tells RowBoat whether or not you want your whole import wrapped in a database transaction.
260
+
261
+ Whatever you define in this method will be merged into the defaults:
262
+
263
+ - `:chunk_size` - `500`
264
+ - `:key_mapping` - `column_mapping`
265
+ - `:recursive` - `false`
266
+ - `:remove_unmapped_keys` - `true`
267
+ - `:validate` - `true`
268
+ - `:value_converters` - `csv_value_converters`
269
+ - `:wrap_in_transaction` - `true`
270
+
271
+ Don't provide `value_converters` or `key_mapping` options here. Implement the [`value_converters`](#value_converters) and [`column_mapping`](#column_mapping) respectively.
272
+
273
+ ### Example
274
+
275
+ ```ruby
276
+ class ImportProduct < RowBoat::Base
277
+ # required configuration omitted for brevity
278
+ def options
279
+ {
280
+ chunk_size: 1000,
281
+ validate: false,
282
+ wrap_in_transaction: false
283
+ }
284
+ end
285
+ end
286
+ ```
287
+
288
+ ## `handle_failed_row`
289
+
290
+ ### Description
291
+
292
+ Implement this to do some work with a row that has failed to import.
293
+
294
+ It's important to note that
295
+ - This happens after the import has completed.
296
+ - The given row is an instance of whatever class was returned by [`import_into`](#import_into).
297
+
298
+ These records are also available in the return value of [`.import`](#import).
299
+
300
+ ### Example
301
+
302
+ ```ruby
303
+ class ImportProduct < RowBoat::Base
304
+ # required configuration omitted for brevity
305
+ def handle_failed_row(row)
306
+ puts row.errors.full_messages.join(", ")
307
+ end
308
+ end
309
+ ```
310
+
311
+ ## `handle_failed_rows`
312
+
313
+ ### Description
314
+
315
+ Override this method to do some work will all of the rows that failed to import.
316
+
317
+ ### Example
318
+
319
+ ```ruby
320
+ class ImportProduct < RowBoat::Base
321
+ # required configuration omitted for brevity
322
+ def handle_failed_rows(rows)
323
+ puts "Failed to import #{rows.size} rows :("
324
+ super
325
+ end
326
+ end
327
+ ```
328
+
329
+ ## `value_converters`
330
+
331
+ ### Description
332
+
333
+ Implement to specify how to translate values from the CSV into whatever sorts of objects you need.
334
+
335
+ Simply return a hash that has the mapped column name (ie, what you mapped it to in the [`column_mapping`](#column_mapping) method) as a key pointing to either
336
+ - a method name as a symbol
337
+ - a proc or lambda
338
+ - an object that implements `convert`
339
+
340
+ Regardless of which one you choose, it takes a value and returns a converted value.
341
+
342
+ This is essentially a sugared up version of `:value_converters` option in [SmarterCSV](https://github.com/tilo/smarter_csv#documentation).
343
+
344
+ ### Example
345
+
346
+ ```ruby
347
+ class ImportProduct < RowBoat::Base
348
+ # required configuration omitted for brevity
349
+ def value_converters
350
+ {
351
+ sell_by: :convert_date,
352
+ name: -> (value) { value.titlelize },
353
+ price: proc { |value| value.to_i },
354
+ description: DescriptionConverter
355
+ }
356
+ end
357
+
358
+ def convert_date(value)
359
+ Date.parse(value) rescue nil
360
+ end
361
+ end
362
+
363
+ module DescriptionConverter
364
+ def self.convert(value)
365
+ value.present? ? value : "default description :("
366
+ end
367
+ end
368
+ ```
369
+
370
+ ## `rollback_transaction?`
371
+
372
+ ### Description
373
+
374
+ Implement this method if you'd like to rollback the transaction after it otherwise has completed.
375
+
376
+ Note: imports are only wrapped in a transaction if the `wrap_in_transaction` option is `true`. It defaults to `true` but this can be configured in [`options`](#options)
377
+
378
+ ### Example
379
+
380
+ ```ruby
381
+ class ImportProduct < RowBoat::Base
382
+ # required configuration omitted for brevity
383
+ def rollback_transaction?
384
+ CsvService.already_imported?(csv_source)
385
+ end
386
+ end
387
+ ```
data/Appraisals ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ appraise "csv_import_1.1_and_0.18" do
4
+ gem "activerecord-import", "~> 0.18.2"
5
+ gem "smarter_csv", "~> 1.1.0"
6
+ end
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at michael.crismali@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in row_boat.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Michael Crismali
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.