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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +63 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/API.md +387 -0
- data/Appraisals +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +102 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +10 -0
- data/db/config.yml +12 -0
- data/db/migrate/20170511160154_create_products.rb +13 -0
- data/db/schema.rb +26 -0
- data/devmynd-logo.png +0 -0
- data/gemfiles/csv_import_1.1_and_0.18.gemfile +8 -0
- data/gemfiles/csv_import_1.1_and_0.18.gemfile.lock +144 -0
- data/lib/row_boat/base.rb +318 -0
- data/lib/row_boat/helpers.rb +55 -0
- data/lib/row_boat/value_converter.rb +16 -0
- data/lib/row_boat/version.rb +5 -0
- data/lib/row_boat.rb +9 -0
- data/row_boat.gemspec +43 -0
- metadata +329 -0
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# RowBoat
|
2
|
+
|
3
|
+
Created by [<img src="https://raw.githubusercontent.com/devmynd/row_boat/master/devmynd-logo.png" alt="DevMynd Logo" />](https://www.devmynd.com/)
|
4
|
+
|
5
|
+
[](http://badge.fury.io/rb/row_boat) [](https://travis-ci.org/devmynd/row_boat)
|
6
|
+
|
7
|
+
A simple gem to help you import CSVs into your ActiveRecord models.
|
8
|
+
|
9
|
+
[Check out the documentation!](/API.md#rowboat-api)
|
10
|
+
|
11
|
+
It uses [SmarterCSV](https://github.com/tilo/smarter_csv) and [`activerecord-import`](https://github.com/zdennis/activerecord-import) to import database records from your CSVs.
|
12
|
+
|
13
|
+
## Contents
|
14
|
+
|
15
|
+
- [Installation](#installation)
|
16
|
+
- [Basic Usage](#basic-usage)
|
17
|
+
- [Development](#development)
|
18
|
+
- [Contributing](#contributing)
|
19
|
+
- [License](#license)
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem "row_boat", "~> 0.4"
|
27
|
+
```
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
$ bundle
|
32
|
+
|
33
|
+
## Basic Usage
|
34
|
+
|
35
|
+
#### [Full documentation can be found here.](/API.md#rowboat-api)
|
36
|
+
|
37
|
+
Below we're defining the required methods ([`import_into`](/API.md#import_into) and [`column_mapping`](/API.md#column_mapping)) and a few additional options as well (via [`value_converters`](/API.md#value_converters) and [`options`](/API.md#options)). Checkout [API.md](/API.md#rowboat-api) for the full documentation for more details :)
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class ImportProduct < RowBoat::Base
|
41
|
+
# required
|
42
|
+
def import_into
|
43
|
+
Product # The ActiveRecord class we want to import records into.
|
44
|
+
end
|
45
|
+
|
46
|
+
# required
|
47
|
+
def column_mapping
|
48
|
+
{
|
49
|
+
# `:prdct_name` is the downcased and symbolized version
|
50
|
+
# of our column header, while `:name` is the attribute
|
51
|
+
# of our model we want to receive `:prdct_name`'s value
|
52
|
+
prdct_name: :name,
|
53
|
+
dllr_amnt: :price_in_cents,
|
54
|
+
desc: :description
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# optional
|
59
|
+
def value_converters
|
60
|
+
{
|
61
|
+
# Allows us to change values we want to import
|
62
|
+
# before we import them
|
63
|
+
price_in_cents: -> (value) { value * 1000 }
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
# optional
|
68
|
+
def preprocess_row(row)
|
69
|
+
if row[:name] && row[:description] && row[:price]
|
70
|
+
row
|
71
|
+
else
|
72
|
+
nil # return nil to skip a row
|
73
|
+
end
|
74
|
+
# we could also remove some attributes or do any
|
75
|
+
# other kind of work we want with the given row.
|
76
|
+
end
|
77
|
+
|
78
|
+
#optional
|
79
|
+
def options
|
80
|
+
{
|
81
|
+
# These are additional configurations that
|
82
|
+
# are generally passed through to SmarterCSV
|
83
|
+
# and activerecord-import
|
84
|
+
validate: false, # this defaults to `true`
|
85
|
+
wrap_in_transaction: false # this defaults to `true`
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
## Development
|
92
|
+
|
93
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests (run `appraisal install` and `appraisal rake` to run the tests against different combinations of dependencies). You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/devmynd/row_boat. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
102
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
require "rubocop/rake_task"
|
6
|
+
require "standalone_migrations"
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
RuboCop::RakeTask.new
|
10
|
+
StandaloneMigrations::Tasks.load_tasks
|
11
|
+
|
12
|
+
task default: %i[rubocop spec]
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "row_boat"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/db/config.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateProducts < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
create_table :products do |t|
|
4
|
+
t.string :name, null: false
|
5
|
+
t.integer :rank, null: false
|
6
|
+
t.text :description
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :products, :rank, unique: true
|
12
|
+
end
|
13
|
+
end
|
data/db/schema.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
4
|
+
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
10
|
+
#
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
12
|
+
|
13
|
+
ActiveRecord::Schema[7.1].define(version: 2017_05_11_160154) do
|
14
|
+
# These are extensions that must be enabled in order to support this database
|
15
|
+
enable_extension "plpgsql"
|
16
|
+
|
17
|
+
create_table "products", force: :cascade do |t|
|
18
|
+
t.string "name", null: false
|
19
|
+
t.integer "rank", null: false
|
20
|
+
t.text "description"
|
21
|
+
t.datetime "created_at", precision: nil, null: false
|
22
|
+
t.datetime "updated_at", precision: nil, null: false
|
23
|
+
t.index ["rank"], name: "index_products_on_rank", unique: true
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/devmynd-logo.png
ADDED
Binary file
|
@@ -0,0 +1,144 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
row_boat (0.5.0)
|
5
|
+
activerecord (>= 5.0.0)
|
6
|
+
activerecord-import (~> 0.18.2)
|
7
|
+
smarter_csv (~> 1.1)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionpack (5.1.0)
|
13
|
+
actionview (= 5.1.0)
|
14
|
+
activesupport (= 5.1.0)
|
15
|
+
rack (~> 2.0)
|
16
|
+
rack-test (~> 0.6.3)
|
17
|
+
rails-dom-testing (~> 2.0)
|
18
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
19
|
+
actionview (5.1.0)
|
20
|
+
activesupport (= 5.1.0)
|
21
|
+
builder (~> 3.1)
|
22
|
+
erubi (~> 1.4)
|
23
|
+
rails-dom-testing (~> 2.0)
|
24
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
25
|
+
activemodel (5.1.0)
|
26
|
+
activesupport (= 5.1.0)
|
27
|
+
activerecord (5.1.0)
|
28
|
+
activemodel (= 5.1.0)
|
29
|
+
activesupport (= 5.1.0)
|
30
|
+
arel (~> 8.0)
|
31
|
+
activerecord-import (0.18.3)
|
32
|
+
activerecord (>= 3.2)
|
33
|
+
activesupport (5.1.0)
|
34
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
35
|
+
i18n (~> 0.7)
|
36
|
+
minitest (~> 5.1)
|
37
|
+
tzinfo (~> 1.1)
|
38
|
+
appraisal (2.2.0)
|
39
|
+
bundler
|
40
|
+
rake
|
41
|
+
thor (>= 0.14.0)
|
42
|
+
arel (8.0.0)
|
43
|
+
ast (2.3.0)
|
44
|
+
awesome_print (1.7.0)
|
45
|
+
builder (3.2.3)
|
46
|
+
coderay (1.1.1)
|
47
|
+
concurrent-ruby (1.0.5)
|
48
|
+
database_cleaner (1.6.1)
|
49
|
+
diff-lcs (1.3)
|
50
|
+
erubi (1.6.0)
|
51
|
+
i18n (0.8.1)
|
52
|
+
loofah (2.0.3)
|
53
|
+
nokogiri (>= 1.5.9)
|
54
|
+
method_source (0.8.2)
|
55
|
+
mini_portile2 (2.1.0)
|
56
|
+
minitest (5.10.2)
|
57
|
+
nokogiri (1.7.2)
|
58
|
+
mini_portile2 (~> 2.1.0)
|
59
|
+
parser (2.4.0.0)
|
60
|
+
ast (~> 2.2)
|
61
|
+
pg (0.20.0)
|
62
|
+
powerpack (0.1.1)
|
63
|
+
pry (0.10.4)
|
64
|
+
coderay (~> 1.1.0)
|
65
|
+
method_source (~> 0.8.1)
|
66
|
+
slop (~> 3.4)
|
67
|
+
pry-doc (0.10.0)
|
68
|
+
pry (~> 0.9)
|
69
|
+
yard (~> 0.9)
|
70
|
+
pry-nav (0.2.4)
|
71
|
+
pry (>= 0.9.10, < 0.11.0)
|
72
|
+
rack (2.0.2)
|
73
|
+
rack-test (0.6.3)
|
74
|
+
rack (>= 1.0)
|
75
|
+
rails-dom-testing (2.0.3)
|
76
|
+
activesupport (>= 4.2.0)
|
77
|
+
nokogiri (>= 1.6)
|
78
|
+
rails-html-sanitizer (1.0.3)
|
79
|
+
loofah (~> 2.0)
|
80
|
+
railties (5.1.0)
|
81
|
+
actionpack (= 5.1.0)
|
82
|
+
activesupport (= 5.1.0)
|
83
|
+
method_source
|
84
|
+
rake (>= 0.8.7)
|
85
|
+
thor (>= 0.18.1, < 2.0)
|
86
|
+
rainbow (2.2.2)
|
87
|
+
rake
|
88
|
+
rake (10.5.0)
|
89
|
+
rspec (3.6.0)
|
90
|
+
rspec-core (~> 3.6.0)
|
91
|
+
rspec-expectations (~> 3.6.0)
|
92
|
+
rspec-mocks (~> 3.6.0)
|
93
|
+
rspec-core (3.6.0)
|
94
|
+
rspec-support (~> 3.6.0)
|
95
|
+
rspec-expectations (3.6.0)
|
96
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
97
|
+
rspec-support (~> 3.6.0)
|
98
|
+
rspec-mocks (3.6.0)
|
99
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
100
|
+
rspec-support (~> 3.6.0)
|
101
|
+
rspec-support (3.6.0)
|
102
|
+
rubocop (0.48.1)
|
103
|
+
parser (>= 2.3.3.1, < 3.0)
|
104
|
+
powerpack (~> 0.1)
|
105
|
+
rainbow (>= 1.99.1, < 3.0)
|
106
|
+
ruby-progressbar (~> 1.7)
|
107
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
108
|
+
ruby-progressbar (1.8.1)
|
109
|
+
slop (3.6.0)
|
110
|
+
smarter_csv (1.1.4)
|
111
|
+
standalone_migrations (5.2.1)
|
112
|
+
activerecord (>= 4.2.7, < 5.2.0)
|
113
|
+
railties (>= 4.2.7, < 5.2.0)
|
114
|
+
rake (~> 10.0)
|
115
|
+
thor (0.19.4)
|
116
|
+
thread_safe (0.3.6)
|
117
|
+
tzinfo (1.2.3)
|
118
|
+
thread_safe (~> 0.1)
|
119
|
+
unicode-display_width (1.2.1)
|
120
|
+
yard (0.9.9)
|
121
|
+
|
122
|
+
PLATFORMS
|
123
|
+
ruby
|
124
|
+
|
125
|
+
DEPENDENCIES
|
126
|
+
activerecord-import (~> 0.18.2)
|
127
|
+
appraisal (~> 2.2.0)
|
128
|
+
awesome_print
|
129
|
+
bundler (~> 1.14)
|
130
|
+
database_cleaner (~> 1.6.0)
|
131
|
+
pg
|
132
|
+
pry
|
133
|
+
pry-doc
|
134
|
+
pry-nav
|
135
|
+
rake (~> 10.0)
|
136
|
+
row_boat!
|
137
|
+
rspec (~> 3.0)
|
138
|
+
rubocop (~> 0.48.1)
|
139
|
+
smarter_csv (~> 1.1.0)
|
140
|
+
standalone_migrations (~> 5.2.0)
|
141
|
+
yard (~> 0.9.9)
|
142
|
+
|
143
|
+
BUNDLED WITH
|
144
|
+
1.15.1
|
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "activerecord-import"
|
5
|
+
require "smarter_csv"
|
6
|
+
|
7
|
+
module RowBoat
|
8
|
+
class Base
|
9
|
+
InvalidColumnMapping = Class.new(StandardError)
|
10
|
+
|
11
|
+
attr_reader :csv_source
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Imports database records from the given CSV-like object.
|
15
|
+
#
|
16
|
+
# @overload import(csv_source)
|
17
|
+
# @param csv_source [String, #read] a CSV-like object that SmarterCSV can read.
|
18
|
+
#
|
19
|
+
# @return [Hash] a hash with +:invalid_records+, +:total_inserts+, +:inserted_ids+, and +:skipped_rows+.
|
20
|
+
#
|
21
|
+
# @see https://github.com/tilo/smarter_csv#documentation SmarterCSV Docs
|
22
|
+
def import(*args, &block)
|
23
|
+
new(*args, &block).import
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Makes a new instance with the given +csv_source+.
|
28
|
+
#
|
29
|
+
# @abstract Override this method if you need additional arguments to process your CSV (like defaults).
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# def initialize(csv_source, default_name)
|
33
|
+
# super(csv_source)
|
34
|
+
# @default_name = default_name
|
35
|
+
# end
|
36
|
+
def initialize(csv_source)
|
37
|
+
@csv_source = csv_source
|
38
|
+
end
|
39
|
+
|
40
|
+
# Parses the csv and inserts/updates the database. You probably won't call this method directly,
|
41
|
+
# instead you would call {RowBoat::Base.import}.
|
42
|
+
#
|
43
|
+
# @return [Hash] a hash with +:invalid_records+, +:total_inserts+, +:inserted_ids+, and +:skipped_rows+.
|
44
|
+
def import
|
45
|
+
import_results = []
|
46
|
+
|
47
|
+
transaction_if_needed do
|
48
|
+
parse_rows do |rows|
|
49
|
+
import_results << import_rows(rows)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
process_import_results(import_results).tap do |total_results|
|
54
|
+
handle_failed_rows(total_results[:invalid_records])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Override with the ActiveRecord class that the CSV should be imported into.
|
59
|
+
#
|
60
|
+
# @abstract
|
61
|
+
#
|
62
|
+
# @note You must implement this method.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# def import_into
|
66
|
+
# Product
|
67
|
+
# end
|
68
|
+
def import_into
|
69
|
+
raise NotImplementedError, not_implemented_error_message(__method__)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Override with a hash that maps CSV column names to their preferred names.
|
73
|
+
# Oftentimes these are the names of the attributes on the model class from {#import_into}.
|
74
|
+
#
|
75
|
+
# @abstract
|
76
|
+
#
|
77
|
+
# @note You must implement this method.
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# def column_mapping
|
81
|
+
# {
|
82
|
+
# prdct_name: :name,
|
83
|
+
# price: :price,
|
84
|
+
# sl_exp: :sale_expires_at
|
85
|
+
# }
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @see #import_into
|
89
|
+
def column_mapping
|
90
|
+
raise NotImplementedError, not_implemented_error_message(__method__)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Override this method if you need to do some work on the row before the record is
|
94
|
+
# inserted/updated or want to skip the row in the import. Simply return +nil+ to skip the row.
|
95
|
+
#
|
96
|
+
# @abstract
|
97
|
+
#
|
98
|
+
# @note If you only need to manipulate one attribute (ie parse a date from a string, etc), then
|
99
|
+
# you should probably use {#value_converters}
|
100
|
+
#
|
101
|
+
# @return [Hash,NilClass] a hash of attributes, +nil+, or even and instance of the class returned
|
102
|
+
# in {#import_into}.
|
103
|
+
#
|
104
|
+
# @see #import_into
|
105
|
+
def preprocess_row(row)
|
106
|
+
row
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def import_rows(rows)
|
111
|
+
import_options = ::RowBoat::Helpers.extract_import_options(merged_options)
|
112
|
+
preprocessed_rows = preprocess_rows(rows)
|
113
|
+
import_into.import(preprocessed_rows, import_options)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Override this method if you need to do something with a chunk of rows.
|
117
|
+
#
|
118
|
+
# @abstract
|
119
|
+
#
|
120
|
+
# @note If you want to filter out a row, you can just return +nil+ from {#preprocess_row}.
|
121
|
+
#
|
122
|
+
# @see #preprocess_row
|
123
|
+
def preprocess_rows(rows)
|
124
|
+
rows.each_with_object([]) do |row, preprocessed_rows|
|
125
|
+
increment_row_number
|
126
|
+
preprocessed_row = preprocess_row(row)
|
127
|
+
preprocessed_row ? preprocessed_rows << preprocessed_row : add_skipped_row(row)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Override this method to specify CSV parsing and importing options.
|
132
|
+
# All SmarterCSV and activerecord-import options can be listed here along with
|
133
|
+
# +:wrap_in_transaction+. The defaults provided by RowBoat can be found in {#default_options}
|
134
|
+
#
|
135
|
+
# @abstract
|
136
|
+
#
|
137
|
+
# @note If you want to use the +:value_converters+ option provided by SmarterCSV
|
138
|
+
# just override {#value_converters}.
|
139
|
+
#
|
140
|
+
# @return [Hash] a hash of configuration options.
|
141
|
+
#
|
142
|
+
# @see https://github.com/tilo/smarter_csv#documentation SmarterCSV docs
|
143
|
+
# @see https://github.com/zdennis/activerecord-import/wiki activerecord-import docs
|
144
|
+
# @see #value_converters
|
145
|
+
def options
|
146
|
+
{}
|
147
|
+
end
|
148
|
+
|
149
|
+
# Default options provided by RowBoat for CSV parsing and importing.
|
150
|
+
#
|
151
|
+
# @note Do not override.
|
152
|
+
#
|
153
|
+
# @return [Hash] a hash of configuration options.
|
154
|
+
#
|
155
|
+
# @api private
|
156
|
+
def default_options
|
157
|
+
{
|
158
|
+
chunk_size: 500,
|
159
|
+
recursive: false,
|
160
|
+
validate: true,
|
161
|
+
value_converters: csv_value_converters,
|
162
|
+
wrap_in_transaction: true
|
163
|
+
}.merge(column_mapping_options)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @api private
|
167
|
+
def merged_options
|
168
|
+
default_options.merge(options)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Override this method to do some work with a row that has failed to import.
|
172
|
+
#
|
173
|
+
# @abstract
|
174
|
+
#
|
175
|
+
# @note +row+ here is actually an instance of the class returned in {#import_into}
|
176
|
+
#
|
177
|
+
# @see #import_into
|
178
|
+
def handle_failed_row(row)
|
179
|
+
row
|
180
|
+
end
|
181
|
+
|
182
|
+
# Override this method to do some work will all of the rows that failed to import.
|
183
|
+
#
|
184
|
+
# @abstract
|
185
|
+
#
|
186
|
+
# @note If you override this method and {#handle_failed_row}, be sure to call +super+.
|
187
|
+
def handle_failed_rows(rows)
|
188
|
+
rows.each { |row| handle_failed_row(row) }
|
189
|
+
end
|
190
|
+
|
191
|
+
# Override this method to specify how to translate values from the CSV
|
192
|
+
# into ruby objects.
|
193
|
+
#
|
194
|
+
# You can provide an object that implements +convert+, a proc or lambda, or the
|
195
|
+
# the name of a method as a Symbol
|
196
|
+
#
|
197
|
+
# @abstract
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# def value_converters
|
201
|
+
# {
|
202
|
+
# name: -> (value) { value.titleize }
|
203
|
+
# price: :convert_price,
|
204
|
+
# expires_at: CustomDateConverter
|
205
|
+
# }
|
206
|
+
# end
|
207
|
+
#
|
208
|
+
# def convert_price(value)
|
209
|
+
# value || 0
|
210
|
+
# end
|
211
|
+
def value_converters
|
212
|
+
{}
|
213
|
+
end
|
214
|
+
|
215
|
+
# @api private
|
216
|
+
def csv_value_converters
|
217
|
+
value_converters.each_with_object({}) do |(key, potential_converter), converters_hash|
|
218
|
+
case potential_converter
|
219
|
+
when Proc
|
220
|
+
converters_hash[key] = ::RowBoat::ValueConverter.new(&potential_converter)
|
221
|
+
when Symbol
|
222
|
+
converters_hash[key] = ::RowBoat::ValueConverter.new { |value| public_send(potential_converter, value) }
|
223
|
+
when nil
|
224
|
+
next
|
225
|
+
else
|
226
|
+
converters_hash[key] = potential_converter
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Implement this method if you'd like to rollback the transaction
|
232
|
+
# after it otherwise has completed.
|
233
|
+
#
|
234
|
+
# @abstract
|
235
|
+
#
|
236
|
+
# @note Only works if the `wrap_in_transaction` option is `true`
|
237
|
+
# (which is the default)
|
238
|
+
#
|
239
|
+
# @return [Boolean]
|
240
|
+
def rollback_transaction?
|
241
|
+
false
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
# @private
|
247
|
+
attr_reader :row_number
|
248
|
+
|
249
|
+
# @api private
|
250
|
+
# @private
|
251
|
+
attr_reader :skipped_rows
|
252
|
+
|
253
|
+
# @api private
|
254
|
+
# @private
|
255
|
+
def increment_row_number
|
256
|
+
@row_number = row_number.to_i + 1
|
257
|
+
end
|
258
|
+
|
259
|
+
def add_skipped_row(row)
|
260
|
+
@skipped_rows ||= []
|
261
|
+
skipped_rows << row
|
262
|
+
end
|
263
|
+
|
264
|
+
# @api private
|
265
|
+
# @private
|
266
|
+
def column_mapping_options
|
267
|
+
case column_mapping
|
268
|
+
when Hash
|
269
|
+
{ key_mapping: column_mapping, remove_unmapped_keys: true }
|
270
|
+
when Array
|
271
|
+
{ user_provided_headers: column_mapping }
|
272
|
+
else
|
273
|
+
raise InvalidColumnMapping, "#column_mapping must be a Hash or an Array: got `#{column_mapping}`"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# @api private
|
278
|
+
# @private
|
279
|
+
def not_implemented_error_message(method_name)
|
280
|
+
"Subclasses of #{self.class.name} must implement `#{method_name}`"
|
281
|
+
end
|
282
|
+
|
283
|
+
# @api private
|
284
|
+
# @private
|
285
|
+
def parse_rows(&block)
|
286
|
+
csv_options = ::RowBoat::Helpers.extract_csv_options(merged_options)
|
287
|
+
::SmarterCSV.process(csv_source, csv_options, &block)
|
288
|
+
end
|
289
|
+
|
290
|
+
# @api private
|
291
|
+
# @private
|
292
|
+
def transaction_if_needed
|
293
|
+
if merged_options[:wrap_in_transaction]
|
294
|
+
import_into.transaction do
|
295
|
+
yield
|
296
|
+
raise ActiveRecord::Rollback if rollback_transaction?
|
297
|
+
end
|
298
|
+
else
|
299
|
+
yield
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# @api private
|
304
|
+
# @private
|
305
|
+
def process_import_results(import_results)
|
306
|
+
import_results.each_with_object(
|
307
|
+
invalid_records: [],
|
308
|
+
total_inserts: 0,
|
309
|
+
inserted_ids: [],
|
310
|
+
skipped_rows: skipped_rows
|
311
|
+
) do |import_result, total_results|
|
312
|
+
total_results[:invalid_records] += import_result.failed_instances
|
313
|
+
total_results[:total_inserts] += import_result.num_inserts
|
314
|
+
total_results[:inserted_ids] += import_result.ids
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|