abstract_importer 1.5.4 → 1.7.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 +5 -5
- data/.github/workflows/ci.yml +29 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +5 -0
- data/README.md +17 -0
- data/abstract_importer.gemspec +4 -5
- data/lib/abstract_importer/strategies/insert_strategy.rb +23 -4
- data/lib/abstract_importer/strategies/upsert_strategy.rb +2 -2
- data/lib/abstract_importer/version.rb +1 -1
- data/test/default_strategy_test.rb +1 -1
- data/test/insert_strategy_test.rb +32 -2
- data/test/support/mock_data_source.rb +6 -0
- data/test/support/mock_objects.rb +7 -0
- data/test/support/schema.rb +7 -0
- data/test/test_helper.rb +9 -5
- data/test/upsert_strategy_test.rb +1 -1
- metadata +17 -31
- data/.travis.yml +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: beb6af75a939202a48788cb3cbd6513c074ff514f1d8c7d104b32700ffdcfe63
|
4
|
+
data.tar.gz: 7ce453f1c101cb56a17642d5b0d21414db6cfd7de3221567f5bce75b4850e93f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b17a2f043ce0a0a7fde5e6de5f1bea224d27142c7bfece25a3e890be4bfb0b4110d6413384949a8d3af4eeec19ad688a8e940556a55e65fa6ef9450468332a20
|
7
|
+
data.tar.gz: 70a4b060155c6f3104f57654340af8dc43816cddbd1a83c49295b647241e326f7e1edc163f7bf673a3d251cf7d02b7734c320cfceae85e3e277705676756f096
|
@@ -0,0 +1,29 @@
|
|
1
|
+
name: Tests
|
2
|
+
on: [push]
|
3
|
+
|
4
|
+
jobs:
|
5
|
+
ruby:
|
6
|
+
name: Tests
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
services:
|
9
|
+
postgres:
|
10
|
+
image: postgres:10.11
|
11
|
+
env:
|
12
|
+
POSTGRES_PASSWORD: password
|
13
|
+
POSTGRES_DB: abstract_importer_test
|
14
|
+
ports:
|
15
|
+
- 5432:5432
|
16
|
+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
17
|
+
steps:
|
18
|
+
- name: Checkout
|
19
|
+
uses: actions/checkout@v2
|
20
|
+
- name: Setup Ruby
|
21
|
+
uses: ruby/setup-ruby@v1
|
22
|
+
with:
|
23
|
+
ruby-version: 2.6
|
24
|
+
bundler-cache: true
|
25
|
+
- name: Run the Tests
|
26
|
+
env:
|
27
|
+
DATABASE_URL: postgres://postgres:password@localhost:5432/abstract_importer_test
|
28
|
+
run: bundle exec rake test
|
29
|
+
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.3
|
1
|
+
2.6.3
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -187,6 +187,23 @@ The following alternate strategies are built in:
|
|
187
187
|
|
188
188
|
Replaces records that have already been imported rather than skipping them.
|
189
189
|
|
190
|
+
##### insert
|
191
|
+
|
192
|
+
Bulk inserts records rather than creating them one-by-one as ActiveRecord objects, skipping those that have already been imported.
|
193
|
+
|
194
|
+
##### upsert
|
195
|
+
|
196
|
+
Bulk inserts records, but updates records that have already been imported rather than skipping them.
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
### Important Note on Upgrading from 1.5.x to 1.6.0
|
201
|
+
|
202
|
+
With the jump to 1.6.0, `abstract_importer` has dropped reliance upon the `activerecord-insert_many` gem in favor of taking advantage of Rails 6's built-in `insert_all` and `upsert_all` for the insert and upsert strategies. With the move from the gem to relying on Rails, the syntax to specify what constitutes a conflict/duplicate record has been updated to align more closely with that of Rails. In particular:
|
203
|
+
|
204
|
+
* Instead of specifying an `on_conflict: { do: :update }` clause, you should specify `on_duplicate: :update` to opt-in to upsert behavior.
|
205
|
+
* Rather than listing columns in `on_conflict: { columns: %i{legacy_id id} }`, you should use `unique_by: %i{legacy_id id}` to specify the index or columns by which a duplicate or conflict is defined. The option follows the behavior of Rails' `upsert_all`, so refer to the Rails 6 documentation for more information.
|
206
|
+
|
190
207
|
|
191
208
|
|
192
209
|
## Contributing
|
data/abstract_importer.gemspec
CHANGED
@@ -17,17 +17,16 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_dependency "activerecord", ">=
|
21
|
-
spec.add_dependency "activesupport", ">=
|
22
|
-
spec.add_dependency "activerecord-insert_many", ">= 0.4.3"
|
20
|
+
spec.add_dependency "activerecord", ">= 6.0"
|
21
|
+
spec.add_dependency "activesupport", ">= 6.0"
|
23
22
|
spec.add_dependency "progressbar"
|
24
23
|
|
25
|
-
spec.add_development_dependency "bundler"
|
24
|
+
spec.add_development_dependency "bundler"
|
26
25
|
spec.add_development_dependency "minitest-reporters"
|
27
26
|
spec.add_development_dependency "minitest-reporters-turn_reporter"
|
28
27
|
spec.add_development_dependency "rake"
|
29
28
|
spec.add_development_dependency "sqlite3"
|
30
|
-
spec.add_development_dependency "pg"
|
29
|
+
spec.add_development_dependency "pg"
|
31
30
|
spec.add_development_dependency "pry"
|
32
31
|
spec.add_development_dependency "rr"
|
33
32
|
spec.add_development_dependency "database_cleaner"
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "abstract_importer/strategies/base"
|
2
|
-
require "activerecord/insert_many"
|
3
2
|
|
4
3
|
module AbstractImporter
|
5
4
|
module Strategies
|
@@ -9,7 +8,8 @@ module AbstractImporter
|
|
9
8
|
super
|
10
9
|
@batch = []
|
11
10
|
@batch_size = options.fetch(:batch_size, 250)
|
12
|
-
@
|
11
|
+
@bulk_operation = options[:on_duplicate] == :update ? :upsert_all : :insert_all
|
12
|
+
@insert_options = options.slice(:unique_by)
|
13
13
|
@insert_options.merge!(returning: [:legacy_id, :id]) if remap_ids?
|
14
14
|
end
|
15
15
|
|
@@ -52,7 +52,13 @@ module AbstractImporter
|
|
52
52
|
|
53
53
|
|
54
54
|
def insert_batch(batch)
|
55
|
-
|
55
|
+
return if batch.empty?
|
56
|
+
|
57
|
+
scope = collection.scope
|
58
|
+
if scope.respond_to?(:proxy_association) && scope.proxy_association.reflection.through_reflection?
|
59
|
+
scope = scope.klass
|
60
|
+
end
|
61
|
+
result = scope.public_send(@bulk_operation, batch, @insert_options)
|
56
62
|
add_batch_to_id_map(result) if remap_ids?
|
57
63
|
end
|
58
64
|
|
@@ -66,13 +72,26 @@ module AbstractImporter
|
|
66
72
|
|
67
73
|
|
68
74
|
def add_batch_to_id_map(result)
|
69
|
-
map = result.each_with_object({}) do |attrs, map|
|
75
|
+
map = cast_result(result, collection.table_name).each_with_object({}) do |attrs, map|
|
70
76
|
map[attrs.fetch("legacy_id")] = attrs.fetch("id")
|
71
77
|
end
|
72
78
|
id_map.merge! collection.table_name, map
|
73
79
|
end
|
74
80
|
|
75
81
|
|
82
|
+
def cast_result(result, table_name)
|
83
|
+
types_by_column = result.columns.each_with_object({}) do |column_name, types|
|
84
|
+
types[column_name] = collection.scope.connection.lookup_cast_type_from_column(collection.scope.columns.find { |column| column.name == column_name })
|
85
|
+
end
|
86
|
+
|
87
|
+
result.to_a.map { |row|
|
88
|
+
Hash[row.map { |column_name, value|
|
89
|
+
[ column_name, types_by_column[column_name].deserialize(value) ]
|
90
|
+
}]
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
|
76
95
|
end
|
77
96
|
end
|
78
97
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "abstract_importer/strategies/insert_strategy"
|
2
|
-
require "activerecord/insert_many"
|
3
2
|
|
4
3
|
module AbstractImporter
|
5
4
|
module Strategies
|
@@ -7,7 +6,8 @@ module AbstractImporter
|
|
7
6
|
|
8
7
|
def initialize(collection, options={})
|
9
8
|
super
|
10
|
-
@
|
9
|
+
@bulk_operation = :upsert_all
|
10
|
+
@insert_options.reverse_merge!(unique_by: remap_ids? ? (association_attrs.keys + %i{legacy_id}) : :id)
|
11
11
|
end
|
12
12
|
|
13
13
|
# We won't skip any records for already being imported
|
@@ -147,7 +147,7 @@ class DefaultStrategyTest < ActiveSupport::TestCase
|
|
147
147
|
import!
|
148
148
|
assert_equal 2, account.students.map(&:pet).compact.count, "Expected two students to still be linked to their pets upon import"
|
149
149
|
assert_kind_of Owl, account.students.find_by_name("Harry Potter").pet, "Expected Harry's pet to be an Owl"
|
150
|
-
assert_kind_of Cat, account.students.find_by_name("Hermione Granger").pet, "Expected
|
150
|
+
assert_kind_of Cat, account.students.find_by_name("Hermione Granger").pet, "Expected Hermione's pet to be a Cat"
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
@@ -4,7 +4,7 @@ require "test_helper"
|
|
4
4
|
class InsertStrategyTest < ActiveSupport::TestCase
|
5
5
|
|
6
6
|
setup do
|
7
|
-
options.merge!(strategy: {students: :insert})
|
7
|
+
options.merge!(strategy: {students: :insert, perils: :insert})
|
8
8
|
end
|
9
9
|
|
10
10
|
|
@@ -17,7 +17,7 @@ class InsertStrategyTest < ActiveSupport::TestCase
|
|
17
17
|
end
|
18
18
|
|
19
19
|
should "import the records in batches" do
|
20
|
-
mock.proxy(Student).
|
20
|
+
mock.proxy(Student).insert_all(satisfy { |arg| arg.length == 3 }, anything)
|
21
21
|
import!
|
22
22
|
assert_equal [456, 457, 458], account.students.pluck(:legacy_id)
|
23
23
|
end
|
@@ -54,6 +54,22 @@ class InsertStrategyTest < ActiveSupport::TestCase
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
context "With an empty data source" do
|
58
|
+
setup do
|
59
|
+
plan do |import|
|
60
|
+
import.students
|
61
|
+
end
|
62
|
+
@data_source = OpenStruct.new
|
63
|
+
@data_source.students = []
|
64
|
+
end
|
65
|
+
|
66
|
+
should "still be able to import" do
|
67
|
+
assert_nothing_raised do
|
68
|
+
import!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
57
73
|
context "When records already exist" do
|
58
74
|
setup do
|
59
75
|
plan do |import|
|
@@ -83,6 +99,20 @@ class InsertStrategyTest < ActiveSupport::TestCase
|
|
83
99
|
end
|
84
100
|
end
|
85
101
|
|
102
|
+
context "When importing a has_many through: relationship" do
|
103
|
+
setup do
|
104
|
+
plan do |import|
|
105
|
+
import.locations
|
106
|
+
import.perils
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
should "handle has_many, through: relations" do
|
111
|
+
import!
|
112
|
+
assert_equal 1, account.perils.count
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
86
116
|
|
87
117
|
|
88
118
|
context "Given an ID generator" do
|
@@ -17,6 +17,7 @@ class Parent < ActiveRecord::Base
|
|
17
17
|
end
|
18
18
|
|
19
19
|
class Location < ActiveRecord::Base
|
20
|
+
has_many :perils
|
20
21
|
validates :slug, format: {with: /\A[a-z0-9\-]+\z/}
|
21
22
|
end
|
22
23
|
|
@@ -37,6 +38,8 @@ class Account < ActiveRecord::Base
|
|
37
38
|
has_many :locations
|
38
39
|
has_many :cats
|
39
40
|
has_many :owls
|
41
|
+
|
42
|
+
has_many :perils, through: :locations
|
40
43
|
end
|
41
44
|
|
42
45
|
class Cat < ActiveRecord::Base
|
@@ -52,3 +55,7 @@ end
|
|
52
55
|
class Ability < ActiveRecord::Base
|
53
56
|
belongs_to :pet, inverse_of: :abilities, polymorphic: true
|
54
57
|
end
|
58
|
+
|
59
|
+
class Peril < ActiveRecord::Base
|
60
|
+
belongs_to :location
|
61
|
+
end
|
data/test/support/schema.rb
CHANGED
@@ -72,4 +72,11 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
72
72
|
t.index "legacy_id", :unique => true
|
73
73
|
end
|
74
74
|
|
75
|
+
create_table "perils", :force => true do |t|
|
76
|
+
t.string "name"
|
77
|
+
t.integer "location_id"
|
78
|
+
t.integer "legacy_id"
|
79
|
+
t.index "legacy_id", :unique => true
|
80
|
+
end
|
81
|
+
|
75
82
|
end
|
data/test/test_helper.rb
CHANGED
@@ -22,11 +22,15 @@ require "minitest/autorun"
|
|
22
22
|
|
23
23
|
system "psql -c 'create database abstract_importer_test'"
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
if ENV["DATABASE_URL"]
|
26
|
+
ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
|
27
|
+
else
|
28
|
+
ActiveRecord::Base.establish_connection(
|
29
|
+
adapter: "postgresql",
|
30
|
+
host: "localhost",
|
31
|
+
database: "abstract_importer_test",
|
32
|
+
verbosity: "quiet")
|
33
|
+
end
|
30
34
|
|
31
35
|
load File.join(File.dirname(__FILE__), "support", "schema.rb")
|
32
36
|
|
@@ -17,7 +17,7 @@ class UpsertStrategyTest < ActiveSupport::TestCase
|
|
17
17
|
end
|
18
18
|
|
19
19
|
should "import the records in batches" do
|
20
|
-
mock.proxy(Student).
|
20
|
+
mock.proxy(Student).upsert_all(satisfy { |arg| arg.length == 3 }, anything)
|
21
21
|
import!
|
22
22
|
assert_equal [456, 457, 458], account.students.pluck(:legacy_id)
|
23
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abstract_importer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Lail
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,42 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '6.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '6.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: activerecord-insert_many
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 0.4.3
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.4.3
|
40
|
+
version: '6.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: progressbar
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,16 +56,16 @@ dependencies:
|
|
70
56
|
name: bundler
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
|
-
- - "
|
59
|
+
- - ">="
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
61
|
+
version: '0'
|
76
62
|
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
|
-
- - "
|
66
|
+
- - ">="
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
68
|
+
version: '0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: minitest-reporters
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -140,16 +126,16 @@ dependencies:
|
|
140
126
|
name: pg
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
|
-
- - "
|
129
|
+
- - ">="
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version: '0
|
131
|
+
version: '0'
|
146
132
|
type: :development
|
147
133
|
prerelease: false
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
149
135
|
requirements:
|
150
|
-
- - "
|
136
|
+
- - ">="
|
151
137
|
- !ruby/object:Gem::Version
|
152
|
-
version: '0
|
138
|
+
version: '0'
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
140
|
name: pry
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -227,9 +213,10 @@ executables: []
|
|
227
213
|
extensions: []
|
228
214
|
extra_rdoc_files: []
|
229
215
|
files:
|
216
|
+
- ".github/workflows/ci.yml"
|
230
217
|
- ".gitignore"
|
231
218
|
- ".ruby-version"
|
232
|
-
-
|
219
|
+
- CHANGELOG.md
|
233
220
|
- Gemfile
|
234
221
|
- LICENSE.txt
|
235
222
|
- README.md
|
@@ -288,8 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
288
275
|
- !ruby/object:Gem::Version
|
289
276
|
version: '0'
|
290
277
|
requirements: []
|
291
|
-
|
292
|
-
rubygems_version: 2.6.11
|
278
|
+
rubygems_version: 3.0.3
|
293
279
|
signing_key:
|
294
280
|
specification_version: 4
|
295
281
|
summary: Provides services for the mass-import of complex relational data
|
data/.travis.yml
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
rvm:
|
3
|
-
- 2.3.1
|
4
|
-
|
5
|
-
# Use Postgres 9.5
|
6
|
-
# https://www.brandur.org/fragments/postgres-95-travis
|
7
|
-
dist: trusty
|
8
|
-
sudo: required
|
9
|
-
addons:
|
10
|
-
postgresql: "9.5"
|
11
|
-
|
12
|
-
before_install: gem update bundler
|
13
|
-
script: bundle exec rake test
|
14
|
-
|
15
|
-
# To stop Travis from running tests for a new commit,
|
16
|
-
# add the following to your commit message: [ci skip]
|
17
|
-
# You should add this when you edit documentation or comments, etc.
|