activerecord-import 0.28.2 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +17 -14
- data/CHANGELOG.md +68 -0
- data/Gemfile +3 -1
- data/README.markdown +50 -58
- data/gemfiles/6.0.gemfile +1 -0
- data/gemfiles/6.1.gemfile +1 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +8 -2
- data/lib/activerecord-import/adapters/mysql_adapter.rb +5 -9
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +11 -12
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +15 -20
- data/lib/activerecord-import/base.rb +8 -1
- data/lib/activerecord-import/import.rb +24 -34
- data/lib/activerecord-import/synchronize.rb +1 -1
- data/lib/activerecord-import/version.rb +1 -1
- data/test/import_test.rb +5 -0
- data/test/schema/postgresql_schema.rb +15 -0
- data/test/support/postgresql/import_examples.rb +41 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +10 -0
- data/test/support/shared_examples/recursive_import.rb +29 -0
- data/test/support/sqlite3/import_examples.rb +2 -15
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cbf4c01d6a3f043ed541f7331d6a19e651cbc035fcc3030b17f1e741a6d4e8b7
|
4
|
+
data.tar.gz: a740491de16c78d4d94ccb428c69342fd8a809f235ce9d42fc41237a4d15c7e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48e62637f6493cd5446cd78aee3db3b4a58e646fb0bc07d3583a662f89f52b03c2a079c83fc2cae807c5577f922e1e3219354cf36dff8ae8cc81ae68266e711f
|
7
|
+
data.tar.gz: 2f40f5fd61975589f8af932a3dbf5b6d14d2cfc6884ebe7019c85956c8c380d3faebffb1b600a7fa04536fe262ed40688946c8d35b8cf5b1bfedd1ddff20af5a
|
data/.travis.yml
CHANGED
@@ -1,30 +1,29 @@
|
|
1
1
|
language: ruby
|
2
2
|
cache: bundler
|
3
3
|
rvm:
|
4
|
-
- 2.
|
4
|
+
- 2.5.5
|
5
5
|
|
6
6
|
env:
|
7
7
|
global:
|
8
8
|
# https://github.com/discourse/discourse/blob/master/.travis.yml
|
9
9
|
- RUBY_GC_MALLOC_LIMIT=50000000
|
10
10
|
matrix:
|
11
|
-
- AR_VERSION=3.2
|
12
|
-
- AR_VERSION=4.0
|
13
|
-
- AR_VERSION=4.1
|
14
|
-
- AR_VERSION=4.2
|
15
|
-
- AR_VERSION=5.0
|
16
11
|
- AR_VERSION=5.1
|
17
12
|
- AR_VERSION=5.2
|
13
|
+
- AR_VERSION=6.0
|
18
14
|
|
19
15
|
matrix:
|
20
16
|
include:
|
21
|
-
- rvm:
|
17
|
+
- rvm: 2.3.8
|
18
|
+
env: AR_VERSION=3.2
|
19
|
+
- rvm: 2.3.8
|
20
|
+
env: AR_VERSION=4.0
|
21
|
+
- rvm: 2.3.8
|
22
|
+
env: AR_VERSION=4.1
|
23
|
+
- rvm: 2.3.8
|
22
24
|
env: AR_VERSION=4.2
|
23
|
-
|
24
|
-
|
25
|
-
- bundle exec rake test:jdbcsqlite3
|
26
|
-
- bundle exec rake test:jdbcmysql
|
27
|
-
- bundle exec rake test:jdbcpostgresql
|
25
|
+
- rvm: 2.3.8
|
26
|
+
env: AR_VERSION=5.0
|
28
27
|
|
29
28
|
fast_finish: true
|
30
29
|
|
@@ -38,7 +37,7 @@ addons:
|
|
38
37
|
- sqlite3
|
39
38
|
- mysql-server
|
40
39
|
- mysql-client
|
41
|
-
- postgresql-9.5-postgis-2.
|
40
|
+
- postgresql-9.5-postgis-2.4
|
42
41
|
|
43
42
|
before_install:
|
44
43
|
- gem update --system
|
@@ -66,6 +65,10 @@ script:
|
|
66
65
|
- bundle exec rake test:sqlite3
|
67
66
|
- bundle exec rubocop
|
68
67
|
|
69
|
-
dist:
|
68
|
+
dist: xenial
|
69
|
+
|
70
|
+
services:
|
71
|
+
- mysql
|
72
|
+
- postgresql
|
70
73
|
|
71
74
|
sudo: required
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,71 @@
|
|
1
|
+
## Changes in 1.0.4
|
2
|
+
|
3
|
+
### Fixes
|
4
|
+
|
5
|
+
* Use prepend pattern for ActiveRecord::Base#establish_connection patching. Thanks to @dombesz via \#648.
|
6
|
+
* Fix NoMethodError when using PostgreSQL ENUM types. Thanks to @sebcoetzee via \#651.
|
7
|
+
* Fix issue updating optimistic lock in Postgres. Thanks to @timanovsky
|
8
|
+
via \#656.
|
9
|
+
|
10
|
+
## Changes in 1.0.3
|
11
|
+
|
12
|
+
### New Features
|
13
|
+
|
14
|
+
* Add support for ActiveRecord 6.1.0.alpha. Thanks to @imtayadeway via
|
15
|
+
\#642.
|
16
|
+
|
17
|
+
### Fixes
|
18
|
+
|
19
|
+
* Return an empty array for results instead of nil when importing empty
|
20
|
+
array. Thanks to @gyfis via \#636.
|
21
|
+
|
22
|
+
## Changes in 1.0.2
|
23
|
+
|
24
|
+
### New Features
|
25
|
+
|
26
|
+
* Add support for CockroachDB adapter. Thanks to @willie via \#605.
|
27
|
+
* Add support for ActiveRecord 6.0.0.rc1. Thanks to @madeindjs, @bill-filler,
|
28
|
+
@jkowens via \#619, \#623.
|
29
|
+
|
30
|
+
### Fixes
|
31
|
+
|
32
|
+
* Fixes NoMethodError when attempting to use nil logger. Thanks to @MattMecel,
|
33
|
+
@khiav22357.
|
34
|
+
* Fix issue validating STI models. Thanks to @thejbsmith, @jkowens via
|
35
|
+
\#626.
|
36
|
+
|
37
|
+
## Changes in 1.0.1
|
38
|
+
|
39
|
+
### Fixes
|
40
|
+
|
41
|
+
* Raise an error with a helpful message if array of values exceeds the number of
|
42
|
+
columns for a table. Thanks to @golddranks via \#589.
|
43
|
+
* Properly check if model responds to import before creating alias.
|
44
|
+
Thanks to @jcw- via \#591.
|
45
|
+
* No longer pass :returning option to child associations on recursive
|
46
|
+
import. Thanks to @dmitriy-kiriyenko via \#595.
|
47
|
+
* Fix import issue for models with Postgresql json/jsonb fields. Thanks
|
48
|
+
to @stokarenko via \#594.
|
49
|
+
* Fix issue importing models with timestamps that contain timezone
|
50
|
+
information. Thaks to @dekaikiwi, @jkowens via \#598.
|
51
|
+
* Ignore :no_returning when using :recursive option. Thanks to @dgollahon, @jkowens
|
52
|
+
via \#599.
|
53
|
+
|
54
|
+
## Changes in 1.0.0
|
55
|
+
|
56
|
+
### New Features
|
57
|
+
|
58
|
+
* Move ActiveRecord::Dirty changes to previous_changes after import.
|
59
|
+
Thanks to @stokarenko via \#584.
|
60
|
+
|
61
|
+
### Breaking Changes
|
62
|
+
|
63
|
+
* Previously :on_duplicate_key_update was enabled by default for MySQL.
|
64
|
+
The update timestamp columns (updated_at, updated_on) would be updated
|
65
|
+
on duplicate key. This was behavior is inconsistent with the other database
|
66
|
+
adapters and could also be considered surprising. Going forward it must
|
67
|
+
be explicitly enabled. See \#548.
|
68
|
+
|
1
69
|
## Changes in 0.28.2
|
2
70
|
|
3
71
|
### Fixes
|
data/Gemfile
CHANGED
@@ -6,6 +6,8 @@ version = ENV['AR_VERSION'].to_f
|
|
6
6
|
|
7
7
|
mysql2_version = '0.3.0'
|
8
8
|
mysql2_version = '0.4.0' if version >= 4.2
|
9
|
+
sqlite3_version = '1.3.0'
|
10
|
+
sqlite3_version = '1.4.0' if version >= 6.0
|
9
11
|
|
10
12
|
group :development, :test do
|
11
13
|
gem 'rubocop', '~> 0.40.0'
|
@@ -16,7 +18,7 @@ end
|
|
16
18
|
platforms :ruby do
|
17
19
|
gem "mysql2", "~> #{mysql2_version}"
|
18
20
|
gem "pg", "~> 0.9"
|
19
|
-
gem "sqlite3", "~>
|
21
|
+
gem "sqlite3", "~> #{sqlite3_version}"
|
20
22
|
gem "seamless_database_pool", "~> 1.0.20"
|
21
23
|
end
|
22
24
|
|
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# Activerecord-Import [![Build Status](https://travis-ci.org/zdennis/activerecord-import.svg?branch=master)](https://travis-ci.org/zdennis/activerecord-import)
|
2
2
|
|
3
|
-
|
3
|
+
Activerecord-Import is a library for bulk inserting data using ActiveRecord.
|
4
4
|
|
5
5
|
One of its major features is following activerecord associations and generating the minimal
|
6
6
|
number of SQL insert statements required, avoiding the N+1 insert problem. An example probably
|
@@ -23,10 +23,10 @@ an 18 hour batch process to <2 hrs.
|
|
23
23
|
|
24
24
|
The gem provides the following high-level features:
|
25
25
|
|
26
|
-
*
|
27
|
-
*
|
28
|
-
*
|
29
|
-
*
|
26
|
+
* Works with raw columns and arrays of values (fastest)
|
27
|
+
* Works with model objects (faster)
|
28
|
+
* Performs validations (fast)
|
29
|
+
* Performs on duplicate key updates (requires MySQL, SQLite 3.24.0+, or Postgres 9.5+)
|
30
30
|
|
31
31
|
## Table of Contents
|
32
32
|
|
@@ -54,6 +54,7 @@ The gem provides the following high-level features:
|
|
54
54
|
* [More Information](#more-information)
|
55
55
|
* [Contributing](#contributing)
|
56
56
|
* [Running Tests](#running-tests)
|
57
|
+
* [Issue Triage](#issue-triage)
|
57
58
|
|
58
59
|
### Examples
|
59
60
|
|
@@ -63,7 +64,7 @@ Without `activerecord-import`, you'd write something like this:
|
|
63
64
|
|
64
65
|
```ruby
|
65
66
|
10.times do |i|
|
66
|
-
Book.create! :
|
67
|
+
Book.create! name: "book #{i}"
|
67
68
|
end
|
68
69
|
```
|
69
70
|
|
@@ -72,7 +73,7 @@ This would end up making 10 SQL calls. YUCK! With `activerecord-import`, you ca
|
|
72
73
|
```ruby
|
73
74
|
books = []
|
74
75
|
10.times do |i|
|
75
|
-
books << Book.new(:
|
76
|
+
books << Book.new(name: "book #{i}")
|
76
77
|
end
|
77
78
|
Book.import books # or use import!
|
78
79
|
```
|
@@ -85,13 +86,13 @@ The `import` method can take an array of column names (string or symbols) and an
|
|
85
86
|
|
86
87
|
```ruby
|
87
88
|
columns = [ :title, :author ]
|
88
|
-
values = [ ['Book1', '
|
89
|
+
values = [ ['Book1', 'George Orwell'], ['Book2', 'Bob Jones'] ]
|
89
90
|
|
90
91
|
# Importing without model validations
|
91
|
-
Book.import columns, values, :
|
92
|
+
Book.import columns, values, validate: false
|
92
93
|
|
93
94
|
# Import with model validations
|
94
|
-
Book.import columns, values, :
|
95
|
+
Book.import columns, values, validate: true
|
95
96
|
|
96
97
|
# when not specified :validate defaults to true
|
97
98
|
Book.import columns, values
|
@@ -102,7 +103,7 @@ Book.import columns, values
|
|
102
103
|
The `import` method can take an array of hashes. The keys map to the column names in the database.
|
103
104
|
|
104
105
|
```ruby
|
105
|
-
values = [{ title: 'Book1', author: '
|
106
|
+
values = [{ title: 'Book1', author: 'George Orwell' }, { title: 'Book2', author: 'Bob Jones'}]
|
106
107
|
|
107
108
|
# Importing without model validations
|
108
109
|
Book.import values, validate: false
|
@@ -113,13 +114,13 @@ Book.import values, validate: true
|
|
113
114
|
# when not specified :validate defaults to true
|
114
115
|
Book.import values
|
115
116
|
```
|
116
|
-
|
117
|
+
#### Import Using Hashes and Explicit Column Names
|
117
118
|
|
118
119
|
The `import` method can take an array of column names and an array of hash objects. The column names are used to determine what fields of data should be imported. The following example will only import books with the `title` field:
|
119
120
|
|
120
121
|
```ruby
|
121
122
|
books = [
|
122
|
-
{ title: "Book 1", author: "
|
123
|
+
{ title: "Book 1", author: "George Orwell" },
|
123
124
|
{ title: "Book 2", author: "Bob Jones" }
|
124
125
|
]
|
125
126
|
columns = [ :title ]
|
@@ -171,15 +172,15 @@ The `import` method can take an array of models. The attributes will be pulled o
|
|
171
172
|
|
172
173
|
```ruby
|
173
174
|
books = [
|
174
|
-
Book.new(:
|
175
|
-
Book.new(:
|
175
|
+
Book.new(title: "Book 1", author: "George Orwell"),
|
176
|
+
Book.new(title: "Book 2", author: "Bob Jones")
|
176
177
|
]
|
177
178
|
|
178
179
|
# without validations
|
179
|
-
Book.import books, :
|
180
|
+
Book.import books, validate: false
|
180
181
|
|
181
182
|
# with validations
|
182
|
-
Book.import books, :
|
183
|
+
Book.import books, validate: true
|
183
184
|
|
184
185
|
# when not specified :validate defaults to true
|
185
186
|
Book.import books
|
@@ -189,16 +190,16 @@ The `import` method can take an array of column names and an array of models. Th
|
|
189
190
|
|
190
191
|
```ruby
|
191
192
|
books = [
|
192
|
-
Book.new(:
|
193
|
-
Book.new(:
|
193
|
+
Book.new(title: "Book 1", author: "George Orwell"),
|
194
|
+
Book.new(title: "Book 2", author: "Bob Jones")
|
194
195
|
]
|
195
196
|
columns = [ :title ]
|
196
197
|
|
197
198
|
# without validations
|
198
|
-
Book.import columns, books, :
|
199
|
+
Book.import columns, books, validate: false
|
199
200
|
|
200
201
|
# with validations
|
201
|
-
Book.import columns, books, :
|
202
|
+
Book.import columns, books, validate: true
|
202
203
|
|
203
204
|
# when not specified :validate defaults to true
|
204
205
|
Book.import columns, books
|
@@ -217,15 +218,15 @@ The `import` method can take a `batch_size` option to control the number of rows
|
|
217
218
|
|
218
219
|
```ruby
|
219
220
|
books = [
|
220
|
-
Book.new(:
|
221
|
-
Book.new(:
|
222
|
-
Book.new(:
|
223
|
-
Book.new(:
|
221
|
+
Book.new(title: "Book 1", author: "George Orwell"),
|
222
|
+
Book.new(title: "Book 2", author: "Bob Jones"),
|
223
|
+
Book.new(title: "Book 1", author: "John Doe"),
|
224
|
+
Book.new(title: "Book 2", author: "Richard Wright")
|
224
225
|
]
|
225
226
|
columns = [ :title ]
|
226
227
|
|
227
228
|
# 2 INSERT statements for 4 records
|
228
|
-
Book.import columns, books, :
|
229
|
+
Book.import columns, books, batch_size: 2
|
229
230
|
```
|
230
231
|
|
231
232
|
#### Recursive
|
@@ -237,8 +238,8 @@ Assume that Books <code>has_many</code> Reviews.
|
|
237
238
|
```ruby
|
238
239
|
books = []
|
239
240
|
10.times do |i|
|
240
|
-
book = Book.new(:
|
241
|
-
book.reviews.build(:
|
241
|
+
book = Book.new(name: "book #{i}")
|
242
|
+
book.reviews.build(title: "Excellent")
|
242
243
|
books << book
|
243
244
|
end
|
244
245
|
Book.import books, recursive: true
|
@@ -248,8 +249,9 @@ Book.import books, recursive: true
|
|
248
249
|
|
249
250
|
Key | Options | Default | Description
|
250
251
|
----------------------- | --------------------- | ------------------ | -----------
|
251
|
-
:validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped).
|
252
|
+
:validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
|
252
253
|
:validate_uniqueness | `true`/`false` | `false` | Whether or not to run uniqueness validations, has potential pitfalls, use with caution (requires `>= v0.27.0`).
|
254
|
+
:validate_with_context | `Symbol` |`:create`/`:update` | Allows passing an ActiveModel validation context for each model. Default is `:create` for new records and `:update` for existing ones.
|
253
255
|
:on_duplicate_key_ignore| `true`/`false` | `false` | Allows skipping records with duplicate keys. See [here](https://github.com/zdennis/activerecord-import/#duplicate-key-ignore) for more details.
|
254
256
|
:ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
|
255
257
|
:on_duplicate_key_update| :all, `Array`, `Hash` | N/A | Allows upsert logic to be used. See [here](https://github.com/zdennis/activerecord-import/#duplicate-key-update) for more details.
|
@@ -257,8 +259,8 @@ Key | Options | Default | Descripti
|
|
257
259
|
:timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
|
258
260
|
:recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
|
259
261
|
:batch_size | `Integer` | total # of records | Max number of records to insert per import
|
260
|
-
:raise_error | `true`/`false` | `false` |
|
261
|
-
|
262
|
+
:raise_error | `true`/`false` | `false` | Raises an exception at the first invalid record. This means there will not be a result object returned. The `import!` method is a shortcut for this.
|
263
|
+
:all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
|
262
264
|
|
263
265
|
#### Duplicate Key Ignore
|
264
266
|
|
@@ -267,14 +269,14 @@ Key | Options | Default | Descripti
|
|
267
269
|
For Postgres 9.5+ it adds `ON CONFLICT DO NOTHING`, for MySQL it uses `INSERT IGNORE`, and for SQLite it uses `INSERT OR IGNORE`. Cannot be enabled on a recursive import. For database adapters that normally support setting primary keys on imported objects, this option prevents that from occurring.
|
268
270
|
|
269
271
|
```ruby
|
270
|
-
book = Book.create! title: "Book1", author: "
|
272
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
271
273
|
book.title = "Updated Book Title"
|
272
274
|
book.author = "Bob Barker"
|
273
275
|
|
274
276
|
Book.import [book], on_duplicate_key_ignore: true
|
275
277
|
|
276
278
|
book.reload.title # => "Book1" (stayed the same)
|
277
|
-
book.reload.author # => "
|
279
|
+
book.reload.author # => "George Orwell" (stayed the same)
|
278
280
|
```
|
279
281
|
|
280
282
|
The option `:on_duplicate_key_ignore` is bypassed when `:recursive` is enabled for [PostgreSQL imports](https://github.com/zdennis/activerecord-import/wiki#recursive-example-postgresql-only).
|
@@ -290,7 +292,7 @@ This will use MySQL's `ON DUPLICATE KEY UPDATE` or Postgres/SQLite `ON CONFLICT
|
|
290
292
|
Basic Update
|
291
293
|
|
292
294
|
```ruby
|
293
|
-
book = Book.create! title: "Book1", author: "
|
295
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
294
296
|
book.title = "Updated Book Title"
|
295
297
|
book.author = "Bob Barker"
|
296
298
|
|
@@ -304,13 +306,13 @@ Book.import [book], on_duplicate_key_update: {conflict_target: [:id], columns: [
|
|
304
306
|
Book.import [book], on_duplicate_key_update: [:title]
|
305
307
|
|
306
308
|
book.reload.title # => "Updated Book Title" (changed)
|
307
|
-
book.reload.author # => "
|
309
|
+
book.reload.author # => "George Orwell" (stayed the same)
|
308
310
|
```
|
309
311
|
|
310
312
|
Using the value from another column
|
311
313
|
|
312
314
|
```ruby
|
313
|
-
book = Book.create! title: "Book1", author: "
|
315
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
314
316
|
book.title = "Updated Book Title"
|
315
317
|
|
316
318
|
# MySQL version
|
@@ -328,7 +330,7 @@ book.reload.author # => "Updated Book Title" (changed)
|
|
328
330
|
Using Custom SQL
|
329
331
|
|
330
332
|
```ruby
|
331
|
-
book = Book.create! title: "Book1", author: "
|
333
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
332
334
|
book.author = "Bob Barker"
|
333
335
|
|
334
336
|
# MySQL version
|
@@ -349,7 +351,7 @@ book.reload.author # => "Bob Barker" (changed)
|
|
349
351
|
PostgreSQL Using constraints
|
350
352
|
|
351
353
|
```ruby
|
352
|
-
book = Book.create! title: "Book1", author: "
|
354
|
+
book = Book.create! title: "Book1", author: "George Orwell", edition: 3, published_at: nil
|
353
355
|
book.published_at = Time.now
|
354
356
|
|
355
357
|
# in migration
|
@@ -363,7 +365,7 @@ Book.import [book], on_duplicate_key_update: {constraint_name: :for_upsert, colu
|
|
363
365
|
|
364
366
|
|
365
367
|
book.reload.title # => "Book1" (stayed the same)
|
366
|
-
book.reload.author # => "
|
368
|
+
book.reload.author # => "George Orwell" (stayed the same)
|
367
369
|
book.reload.edition # => 3 (stayed the same)
|
368
370
|
book.reload.published_at # => 2017-10-09 (changed)
|
369
371
|
```
|
@@ -383,7 +385,7 @@ articles = [
|
|
383
385
|
Article.new(author_id: 3, content: '')
|
384
386
|
]
|
385
387
|
|
386
|
-
demo = Article.import(articles
|
388
|
+
demo = Article.import(articles, returning: :title) # => #<struct ActiveRecord::Import::Result
|
387
389
|
|
388
390
|
demo.failed_instances
|
389
391
|
=> [#<Article id: 3, author_id: 3, title: nil, content: "", created_at: nil, updated_at: nil>]
|
@@ -397,7 +399,7 @@ demo.ids
|
|
397
399
|
|
398
400
|
demo.results
|
399
401
|
=> ["First Article", "Second Article"] # for Postgres
|
400
|
-
=> [] for other DBs
|
402
|
+
=> [] # for other DBs
|
401
403
|
```
|
402
404
|
|
403
405
|
### Counter Cache
|
@@ -552,7 +554,7 @@ When rubygems pushes the `lib` folder onto the load path a `require` will now fi
|
|
552
554
|
|
553
555
|
### Conflicts With Other Gems
|
554
556
|
|
555
|
-
|
557
|
+
Activerecord-Import adds the `.import` method onto `ActiveRecord::Base`. There are other gems, such as `elasticsearch-rails`, that do the same thing. In conflicts such as this, there is an aliased method named `.bulk_import` that can be used interchangeably.
|
556
558
|
|
557
559
|
If you are using the `apartment` gem, there is a weird triple interaction between that gem, `activerecord-import`, and `activerecord` involving caching of the `sequence_name` of a model. This can be worked around by explcitly setting this value within the model. For example:
|
558
560
|
|
@@ -579,7 +581,7 @@ See https://github.com/zdennis/activerecord-import/issues/233 for further discus
|
|
579
581
|
|
580
582
|
### More Information
|
581
583
|
|
582
|
-
For more information on
|
584
|
+
For more information on Activerecord-Import please see its wiki: https://github.com/zdennis/activerecord-import/wiki
|
583
585
|
|
584
586
|
To document new information, please add to the README instead of the wiki. See https://github.com/zdennis/activerecord-import/issues/397 for discussion.
|
585
587
|
|
@@ -605,6 +607,10 @@ AR_VERSION=4.2 bundle exec rake test:postgresql test:sqlite3 test:mysql2
|
|
605
607
|
|
606
608
|
Once you have pushed up your changes, you can find your CI results [here](https://travis-ci.org/zdennis/activerecord-import/).
|
607
609
|
|
610
|
+
## Issue Triage [![Open Source Helpers](https://www.codetriage.com/zdennis/activerecord-import/badges/users.svg)](https://www.codetriage.com/zdennis/activerecord-import)
|
611
|
+
|
612
|
+
You can triage issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to activerecord-import on CodeTriage](https://www.codetriage.com/zdennis/activerecord-import).
|
613
|
+
|
608
614
|
# License
|
609
615
|
|
610
616
|
This is licensed under the ruby license.
|
@@ -612,17 +618,3 @@ This is licensed under the ruby license.
|
|
612
618
|
# Author
|
613
619
|
|
614
620
|
Zach Dennis (zach.dennis@gmail.com)
|
615
|
-
|
616
|
-
# Contributors
|
617
|
-
|
618
|
-
* Jordan Owens (@jkowens)
|
619
|
-
* Erik Michaels-Ober (@sferik)
|
620
|
-
* Blythe Dunham
|
621
|
-
* Gabe da Silveira
|
622
|
-
* Henry Work
|
623
|
-
* James Herdman
|
624
|
-
* Marcus Crafter
|
625
|
-
* Thibaud Guillaume-Gentil
|
626
|
-
* Mark Van Holstyn
|
627
|
-
* Victor Costan
|
628
|
-
* Dillon Welch
|
@@ -0,0 +1 @@
|
|
1
|
+
gem 'activerecord', '~> 6.0.0'
|
@@ -0,0 +1 @@
|
|
1
|
+
gem 'activerecord', '~> 6.1.0.alpha', github: "rails/rails"
|
@@ -46,7 +46,7 @@ module ActiveRecord::Import::AbstractAdapter
|
|
46
46
|
|
47
47
|
if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
|
48
48
|
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key], options[:locking_column] )
|
49
|
-
elsif options[:on_duplicate_key_update]
|
49
|
+
elsif logger && options[:on_duplicate_key_update]
|
50
50
|
logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
|
51
51
|
end
|
52
52
|
|
@@ -59,8 +59,14 @@ module ActiveRecord::Import::AbstractAdapter
|
|
59
59
|
post_sql_statements
|
60
60
|
end
|
61
61
|
|
62
|
+
def increment_locking_column!(table_name, results, locking_column)
|
63
|
+
if locking_column.present?
|
64
|
+
results << "\"#{locking_column}\"=#{table_name}.\"#{locking_column}\"+1"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
62
68
|
def supports_on_duplicate_key_update?
|
63
|
-
|
69
|
+
true
|
64
70
|
end
|
65
71
|
end
|
66
72
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord::Import::MysqlAdapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
|
-
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
3
|
|
5
4
|
NO_MAX_PACKET = 0
|
6
5
|
QUERY_OVERHEAD = 8 # This was shown to be true for MySQL, but it's not clear where the overhead is from.
|
@@ -71,14 +70,11 @@ module ActiveRecord::Import::MysqlAdapter
|
|
71
70
|
|
72
71
|
# Add a column to be updated on duplicate key update
|
73
72
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
74
|
-
if options
|
75
|
-
columns = options[:on_duplicate_key_update]
|
73
|
+
if (columns = options[:on_duplicate_key_update])
|
76
74
|
case columns
|
77
75
|
when Array then columns << column.to_sym unless columns.include?(column.to_sym)
|
78
76
|
when Hash then columns[column.to_sym] = column.to_sym
|
79
77
|
end
|
80
|
-
elsif !options[:ignore] && !options[:on_duplicate_key_ignore]
|
81
|
-
options[:on_duplicate_key_update] = [column.to_sym]
|
82
78
|
end
|
83
79
|
end
|
84
80
|
|
@@ -105,7 +101,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
105
101
|
qc = quote_column_name( column )
|
106
102
|
"#{table_name}.#{qc}=VALUES(#{qc})"
|
107
103
|
end
|
108
|
-
increment_locking_column!(
|
104
|
+
increment_locking_column!(table_name, results, locking_column)
|
109
105
|
results.join( ',' )
|
110
106
|
end
|
111
107
|
|
@@ -115,7 +111,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
115
111
|
qc2 = quote_column_name( column2 )
|
116
112
|
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
117
113
|
end
|
118
|
-
increment_locking_column!(
|
114
|
+
increment_locking_column!(table_name, results, locking_column)
|
119
115
|
results.join( ',')
|
120
116
|
end
|
121
117
|
|
@@ -124,9 +120,9 @@ module ActiveRecord::Import::MysqlAdapter
|
|
124
120
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
|
125
121
|
end
|
126
122
|
|
127
|
-
def increment_locking_column!(
|
123
|
+
def increment_locking_column!(table_name, results, locking_column)
|
128
124
|
if locking_column.present?
|
129
|
-
results << "
|
125
|
+
results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
|
130
126
|
end
|
131
127
|
end
|
132
128
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord::Import::PostgreSQLAdapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
|
-
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
3
|
|
5
4
|
MIN_VERSION_FOR_UPSERT = 90_500
|
6
5
|
|
@@ -19,7 +18,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
19
18
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
20
19
|
|
21
20
|
columns = returning_columns(options)
|
22
|
-
if columns.blank? || options[:no_returning]
|
21
|
+
if columns.blank? || (options[:no_returning] && !options[:recursive])
|
23
22
|
insert( sql2insert, *args )
|
24
23
|
else
|
25
24
|
returned_values = if columns.size > 1
|
@@ -73,14 +72,14 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
73
72
|
if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
|
74
73
|
sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
|
75
74
|
end
|
76
|
-
elsif options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
75
|
+
elsif logger && options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
77
76
|
logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
|
78
77
|
end
|
79
78
|
|
80
79
|
sql += super(table_name, options)
|
81
80
|
|
82
81
|
columns = returning_columns(options)
|
83
|
-
unless columns.blank? || options[:no_returning]
|
82
|
+
unless columns.blank? || (options[:no_returning] && !options[:recursive])
|
84
83
|
sql << " RETURNING \"#{columns.join('", "')}\""
|
85
84
|
end
|
86
85
|
|
@@ -158,7 +157,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
158
157
|
qc = quote_column_name( column )
|
159
158
|
"#{qc}=EXCLUDED.#{qc}"
|
160
159
|
end
|
161
|
-
increment_locking_column!(results, locking_column)
|
160
|
+
increment_locking_column!(table_name, results, locking_column)
|
162
161
|
results.join( ',' )
|
163
162
|
end
|
164
163
|
|
@@ -168,7 +167,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
168
167
|
qc2 = quote_column_name( column2 )
|
169
168
|
"#{qc1}=EXCLUDED.#{qc2}"
|
170
169
|
end
|
171
|
-
increment_locking_column!(results, locking_column)
|
170
|
+
increment_locking_column!(table_name, results, locking_column)
|
172
171
|
results.join( ',' )
|
173
172
|
end
|
174
173
|
|
@@ -195,17 +194,17 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
195
194
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
|
196
195
|
end
|
197
196
|
|
198
|
-
def supports_on_duplicate_key_update?
|
199
|
-
|
197
|
+
def supports_on_duplicate_key_update?
|
198
|
+
database_version >= MIN_VERSION_FOR_UPSERT
|
200
199
|
end
|
201
200
|
|
202
201
|
def supports_setting_primary_key_of_imported_objects?
|
203
202
|
true
|
204
203
|
end
|
205
204
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
205
|
+
private
|
206
|
+
|
207
|
+
def database_version
|
208
|
+
defined?(postgresql_version) ? postgresql_version : super
|
210
209
|
end
|
211
210
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord::Import::SQLite3Adapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
|
-
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
3
|
|
5
4
|
MIN_VERSION_FOR_IMPORT = "3.7.11".freeze
|
6
5
|
MIN_VERSION_FOR_UPSERT = "3.24.0".freeze
|
@@ -9,16 +8,12 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
9
8
|
# Override our conformance to ActiveRecord::Import::ImportSupport interface
|
10
9
|
# to ensure that we only support import in supported version of SQLite.
|
11
10
|
# Which INSERT statements with multiple value sets was introduced in 3.7.11.
|
12
|
-
def supports_import?
|
13
|
-
|
14
|
-
true
|
15
|
-
else
|
16
|
-
false
|
17
|
-
end
|
11
|
+
def supports_import?
|
12
|
+
database_version >= MIN_VERSION_FOR_IMPORT
|
18
13
|
end
|
19
14
|
|
20
|
-
def supports_on_duplicate_key_update?
|
21
|
-
|
15
|
+
def supports_on_duplicate_key_update?
|
16
|
+
database_version >= MIN_VERSION_FOR_UPSERT
|
22
17
|
end
|
23
18
|
|
24
19
|
# +sql+ can be a single string or an array. If it is an array all
|
@@ -96,7 +91,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
96
91
|
|
97
92
|
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
|
98
93
|
# in +args+.
|
99
|
-
def sql_for_on_duplicate_key_update(
|
94
|
+
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
100
95
|
arg, primary_key, locking_column = args
|
101
96
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
102
97
|
return unless arg.is_a?( Hash )
|
@@ -117,9 +112,9 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
117
112
|
|
118
113
|
sql << "#{conflict_target}DO UPDATE SET "
|
119
114
|
if columns.is_a?( Array )
|
120
|
-
sql << sql_for_on_duplicate_key_update_as_array( locking_column, columns )
|
115
|
+
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
|
121
116
|
elsif columns.is_a?( Hash )
|
122
|
-
sql << sql_for_on_duplicate_key_update_as_hash( locking_column, columns )
|
117
|
+
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
|
123
118
|
elsif columns.is_a?( String )
|
124
119
|
sql << columns
|
125
120
|
else
|
@@ -131,22 +126,22 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
131
126
|
sql
|
132
127
|
end
|
133
128
|
|
134
|
-
def sql_for_on_duplicate_key_update_as_array( locking_column, arr ) # :nodoc:
|
129
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
|
135
130
|
results = arr.map do |column|
|
136
131
|
qc = quote_column_name( column )
|
137
132
|
"#{qc}=EXCLUDED.#{qc}"
|
138
133
|
end
|
139
|
-
increment_locking_column!(results, locking_column)
|
134
|
+
increment_locking_column!(table_name, results, locking_column)
|
140
135
|
results.join( ',' )
|
141
136
|
end
|
142
137
|
|
143
|
-
def sql_for_on_duplicate_key_update_as_hash( locking_column, hsh ) # :nodoc:
|
138
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
|
144
139
|
results = hsh.map do |column1, column2|
|
145
140
|
qc1 = quote_column_name( column1 )
|
146
141
|
qc2 = quote_column_name( column2 )
|
147
142
|
"#{qc1}=EXCLUDED.#{qc2}"
|
148
143
|
end
|
149
|
-
increment_locking_column!(results, locking_column)
|
144
|
+
increment_locking_column!(table_name, results, locking_column)
|
150
145
|
results.join( ',' )
|
151
146
|
end
|
152
147
|
|
@@ -170,9 +165,9 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
170
165
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
|
171
166
|
end
|
172
167
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
168
|
+
private
|
169
|
+
|
170
|
+
def database_version
|
171
|
+
defined?(sqlite_version) ? sqlite_version : super
|
177
172
|
end
|
178
173
|
end
|
@@ -13,6 +13,7 @@ module ActiveRecord::Import
|
|
13
13
|
when 'postgresql_makara' then 'postgresql'
|
14
14
|
when 'makara_postgis' then 'postgresql'
|
15
15
|
when 'postgis' then 'postgresql'
|
16
|
+
when 'cockroachdb' then 'postgresql'
|
16
17
|
else adapter
|
17
18
|
end
|
18
19
|
end
|
@@ -26,7 +27,13 @@ module ActiveRecord::Import
|
|
26
27
|
|
27
28
|
# Loads the import functionality for the passed in ActiveRecord connection
|
28
29
|
def self.load_from_connection_pool(connection_pool)
|
29
|
-
|
30
|
+
adapter =
|
31
|
+
if connection_pool.respond_to?(:db_config) # ActiveRecord >= 6.1
|
32
|
+
connection_pool.db_config.adapter
|
33
|
+
else
|
34
|
+
connection_pool.spec.config[:adapter]
|
35
|
+
end
|
36
|
+
require_adapter adapter
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
@@ -11,12 +11,6 @@ module ActiveRecord::Import #:nodoc:
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
module OnDuplicateKeyUpdateSupport #:nodoc:
|
15
|
-
def supports_on_duplicate_key_update? #:nodoc:
|
16
|
-
true
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
14
|
class MissingColumnError < StandardError
|
21
15
|
def initialize(name, index)
|
22
16
|
super "Missing column for value <#{name}> at index #{index}"
|
@@ -26,6 +20,7 @@ module ActiveRecord::Import #:nodoc:
|
|
26
20
|
class Validator
|
27
21
|
def initialize(klass, options = {})
|
28
22
|
@options = options
|
23
|
+
@validator_class = klass
|
29
24
|
init_validations(klass)
|
30
25
|
end
|
31
26
|
|
@@ -70,6 +65,8 @@ module ActiveRecord::Import #:nodoc:
|
|
70
65
|
end
|
71
66
|
|
72
67
|
def valid_model?(model)
|
68
|
+
init_validations(model.class) unless model.class == @validator_class
|
69
|
+
|
73
70
|
validation_context = @options[:validate_with_context]
|
74
71
|
validation_context ||= (model.new_record? ? :create : :update)
|
75
72
|
current_context = model.send(:validation_context)
|
@@ -242,16 +239,16 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
242
239
|
alias import bulk_import unless respond_to? :import
|
243
240
|
end
|
244
241
|
|
242
|
+
module ActiveRecord::Import::Connection
|
243
|
+
def establish_connection(args = nil)
|
244
|
+
super(args)
|
245
|
+
ActiveRecord::Import.load_from_connection_pool connection_pool
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
245
249
|
class ActiveRecord::Base
|
246
250
|
class << self
|
247
|
-
|
248
|
-
conn = establish_connection_without_activerecord_import(*args)
|
249
|
-
ActiveRecord::Import.load_from_connection_pool connection_pool
|
250
|
-
conn
|
251
|
-
end
|
252
|
-
|
253
|
-
alias establish_connection_without_activerecord_import establish_connection
|
254
|
-
alias establish_connection establish_connection_with_activerecord_import
|
251
|
+
prepend ActiveRecord::Import::Connection
|
255
252
|
|
256
253
|
# Returns true if the current database connection adapter
|
257
254
|
# supports import functionality, otherwise returns false.
|
@@ -528,7 +525,7 @@ class ActiveRecord::Base
|
|
528
525
|
import_helper(*args)
|
529
526
|
end
|
530
527
|
end
|
531
|
-
alias import bulk_import unless respond_to? :import
|
528
|
+
alias import bulk_import unless ActiveRecord::Base.respond_to? :import
|
532
529
|
|
533
530
|
# Imports a collection of values if all values are valid. Import fails at the
|
534
531
|
# first encountered validation error and raises ActiveRecord::RecordInvalid
|
@@ -540,7 +537,7 @@ class ActiveRecord::Base
|
|
540
537
|
|
541
538
|
bulk_import(*args, options)
|
542
539
|
end
|
543
|
-
alias import! bulk_import! unless respond_to? :import!
|
540
|
+
alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
|
544
541
|
|
545
542
|
def import_helper( *args )
|
546
543
|
options = { validate: true, timestamps: true }
|
@@ -574,15 +571,6 @@ class ActiveRecord::Base
|
|
574
571
|
end
|
575
572
|
end
|
576
573
|
|
577
|
-
default_values = column_defaults
|
578
|
-
stored_attrs = respond_to?(:stored_attributes) ? stored_attributes : {}
|
579
|
-
serialized_attrs = if defined?(ActiveRecord::Type::Serialized)
|
580
|
-
attrs = column_names.select { |c| type_for_attribute(c.to_s).class == ActiveRecord::Type::Serialized }
|
581
|
-
Hash[attrs.map { |a| [a, nil] }]
|
582
|
-
else
|
583
|
-
serialized_attributes
|
584
|
-
end
|
585
|
-
|
586
574
|
update_attrs = if record_timestamps && options[:timestamps]
|
587
575
|
if respond_to?(:timestamp_attributes_for_update, true)
|
588
576
|
send(:timestamp_attributes_for_update).map(&:to_sym)
|
@@ -608,12 +596,8 @@ class ActiveRecord::Base
|
|
608
596
|
update_attrs && update_attrs.include?(name.to_sym) &&
|
609
597
|
!model.send("#{name}_changed?")
|
610
598
|
nil
|
611
|
-
elsif stored_attrs.key?(name.to_sym) ||
|
612
|
-
serialized_attrs.key?(name.to_s) ||
|
613
|
-
default_values[name.to_s]
|
614
|
-
model.read_attribute(name.to_s)
|
615
599
|
else
|
616
|
-
model.
|
600
|
+
model.read_attribute(name.to_s)
|
617
601
|
end
|
618
602
|
end
|
619
603
|
end
|
@@ -640,7 +624,7 @@ class ActiveRecord::Base
|
|
640
624
|
end
|
641
625
|
# supports empty array
|
642
626
|
elsif args.last.is_a?( Array ) && args.last.empty?
|
643
|
-
return ActiveRecord::Import::Result.new([], 0, [])
|
627
|
+
return ActiveRecord::Import::Result.new([], 0, [], [])
|
644
628
|
# supports 2-element array and array
|
645
629
|
elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
|
646
630
|
|
@@ -868,7 +852,10 @@ class ActiveRecord::Base
|
|
868
852
|
end
|
869
853
|
|
870
854
|
models.each do |model|
|
871
|
-
if model.respond_to?(:
|
855
|
+
if model.respond_to?(:changes_applied) # Rails 4.1.8 and higher
|
856
|
+
model.changes_internally_applied if model.respond_to?(:changes_internally_applied) # legacy behavior for Rails 5.1
|
857
|
+
model.changes_applied
|
858
|
+
elsif model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
|
872
859
|
model.clear_changes_information
|
873
860
|
else # Rails 3.2
|
874
861
|
model.instance_variable_get(:@changed_attributes).clear
|
@@ -901,8 +888,9 @@ class ActiveRecord::Base
|
|
901
888
|
associated_objects_by_class = {}
|
902
889
|
models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
|
903
890
|
|
904
|
-
# :on_duplicate_key_update not supported for associations
|
891
|
+
# :on_duplicate_key_update and :returning not supported for associations
|
905
892
|
options.delete(:on_duplicate_key_update)
|
893
|
+
options.delete(:returning)
|
906
894
|
|
907
895
|
associated_objects_by_class.each_value do |associations|
|
908
896
|
associations.each_value do |associated_records|
|
@@ -970,9 +958,11 @@ class ActiveRecord::Base
|
|
970
958
|
val = serialized_attributes[column.name].dump(val)
|
971
959
|
end
|
972
960
|
# Fixes #443 to support binary (i.e. bytea) columns on PG
|
973
|
-
val = column.type_cast(val) unless column.type.to_sym == :binary
|
961
|
+
val = column.type_cast(val) unless column.type && column.type.to_sym == :binary
|
974
962
|
connection_memo.quote(val, column)
|
975
963
|
end
|
964
|
+
else
|
965
|
+
raise ArgumentError, "Number of values (#{arr.length}) exceeds number of columns (#{columns.length})"
|
976
966
|
end
|
977
967
|
end
|
978
968
|
"(#{my_values.join(',')})"
|
@@ -39,8 +39,8 @@ module ActiveRecord # :nodoc:
|
|
39
39
|
|
40
40
|
next unless matched_instance
|
41
41
|
|
42
|
-
instance.send :clear_aggregation_cache
|
43
42
|
instance.send :clear_association_cache
|
43
|
+
instance.send :clear_aggregation_cache if instance.respond_to?(:clear_aggregation_cache, true)
|
44
44
|
instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
|
45
45
|
|
46
46
|
if instance.respond_to?(:clear_changes_information)
|
data/test/import_test.rb
CHANGED
@@ -17,6 +17,11 @@ describe "#import" do
|
|
17
17
|
assert_equal error.message, "Last argument should be a two dimensional array '[[]]'. First element in array was a String"
|
18
18
|
end
|
19
19
|
|
20
|
+
it "warns you that you're passing more data than you ought to" do
|
21
|
+
error = assert_raise(ArgumentError) { Topic.import %w(title author_name), [['Author #1', 'Book #1', 0]] }
|
22
|
+
assert_equal error.message, "Number of values (8) exceeds number of columns (7)"
|
23
|
+
end
|
24
|
+
|
20
25
|
it "should not produce an error when importing empty arrays" do
|
21
26
|
assert_nothing_raised do
|
22
27
|
Topic.import []
|
@@ -3,11 +3,23 @@ ActiveRecord::Schema.define do
|
|
3
3
|
execute('CREATE extension IF NOT EXISTS "pgcrypto";')
|
4
4
|
execute('CREATE extension IF NOT EXISTS "uuid-ossp";')
|
5
5
|
|
6
|
+
# create ENUM if it does not exist yet
|
7
|
+
begin
|
8
|
+
execute('CREATE TYPE vendor_type AS ENUM (\'wholesaler\', \'retailer\');')
|
9
|
+
rescue ActiveRecord::StatementInvalid => e
|
10
|
+
# since PostgreSQL does not support IF NOT EXISTS when creating a TYPE,
|
11
|
+
# rescue the error and check the error class
|
12
|
+
raise unless e.cause.is_a? PG::DuplicateObject
|
13
|
+
execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'wholesaler\';')
|
14
|
+
execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'retailer\';')
|
15
|
+
end
|
16
|
+
|
6
17
|
create_table :vendors, id: :uuid, force: :cascade do |t|
|
7
18
|
t.string :name, null: true
|
8
19
|
t.text :preferences
|
9
20
|
|
10
21
|
if t.respond_to?(:json)
|
22
|
+
t.json :pure_json_data
|
11
23
|
t.json :data
|
12
24
|
else
|
13
25
|
t.text :data
|
@@ -20,6 +32,7 @@ ActiveRecord::Schema.define do
|
|
20
32
|
end
|
21
33
|
|
22
34
|
if t.respond_to?(:jsonb)
|
35
|
+
t.jsonb :pure_jsonb_data
|
23
36
|
t.jsonb :settings
|
24
37
|
t.jsonb :json_data, null: false, default: {}
|
25
38
|
else
|
@@ -27,6 +40,8 @@ ActiveRecord::Schema.define do
|
|
27
40
|
t.text :json_data
|
28
41
|
end
|
29
42
|
|
43
|
+
t.column :vendor_type, :vendor_type
|
44
|
+
|
30
45
|
t.datetime :created_at
|
31
46
|
t.datetime :updated_at
|
32
47
|
end
|
@@ -37,6 +37,12 @@ def should_support_postgresql_import_functionality
|
|
37
37
|
assert !topic.changed?
|
38
38
|
end
|
39
39
|
|
40
|
+
if ENV['AR_VERSION'].to_f > 4.1
|
41
|
+
it "moves the dirty changes to previous_changes" do
|
42
|
+
assert topic.previous_changes.present?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
40
46
|
it "marks models as persisted" do
|
41
47
|
assert !topic.new_record?
|
42
48
|
assert topic.persisted?
|
@@ -110,6 +116,17 @@ def should_support_postgresql_import_functionality
|
|
110
116
|
assert_equal [%w(King It)], result.results
|
111
117
|
end
|
112
118
|
|
119
|
+
context "when given an empty array" do
|
120
|
+
let(:result) { Book.import([], returning: %w(title)) }
|
121
|
+
|
122
|
+
setup { result }
|
123
|
+
|
124
|
+
it "returns empty arrays for ids and results" do
|
125
|
+
assert_equal [], result.ids
|
126
|
+
assert_equal [], result.results
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
113
130
|
context "when primary key and returning overlap" do
|
114
131
|
let(:result) { Book.import(books, returning: %w(id title)) }
|
115
132
|
|
@@ -228,6 +245,30 @@ def should_support_postgresql_import_functionality
|
|
228
245
|
assert_equal({}, Vendor.first.json_data)
|
229
246
|
end
|
230
247
|
end
|
248
|
+
|
249
|
+
%w(json jsonb).each do |json_type|
|
250
|
+
describe "with pure #{json_type} fields" do
|
251
|
+
let(:data) { { a: :b } }
|
252
|
+
let(:json_field_name) { "pure_#{json_type}_data" }
|
253
|
+
it "imports the values from saved records" do
|
254
|
+
vendor = Vendor.create!(name: 'Vendor 1', json_field_name => data)
|
255
|
+
|
256
|
+
Vendor.import [vendor], on_duplicate_key_update: [json_field_name]
|
257
|
+
assert_equal(data.as_json, vendor.reload[json_field_name])
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "with enum field" do
|
264
|
+
let(:vendor_type) { "retailer" }
|
265
|
+
it "imports the correct values for enum fields" do
|
266
|
+
vendor = Vendor.new(name: 'Vendor 1', vendor_type: vendor_type)
|
267
|
+
assert_difference "Vendor.count", +1 do
|
268
|
+
Vendor.import [vendor]
|
269
|
+
end
|
270
|
+
assert_equal(vendor_type, Vendor.first.vendor_type)
|
271
|
+
end
|
231
272
|
end
|
232
273
|
|
233
274
|
describe "with binary field" do
|
@@ -73,6 +73,16 @@ def should_support_basic_on_duplicate_key_update
|
|
73
73
|
assert_equal user.name, users[i].name + ' Rothschild'
|
74
74
|
assert_equal 1, user.lock_version
|
75
75
|
end
|
76
|
+
updated_values2 = User.all.map do |user|
|
77
|
+
user.name += ' jr.'
|
78
|
+
{ id: user.id, name: user.name }
|
79
|
+
end
|
80
|
+
User.import(updated_values2, on_duplicate_key_update: [:name])
|
81
|
+
assert User.count == updated_values2.length
|
82
|
+
User.all.each_with_index do |user, i|
|
83
|
+
assert_equal user.name, users[i].name + ' Rothschild jr.'
|
84
|
+
assert_equal 2, user.lock_version
|
85
|
+
end
|
76
86
|
end
|
77
87
|
|
78
88
|
it 'upsert optimistic lock columns other than lock_version by model' do
|
@@ -183,5 +183,34 @@ def should_support_recursive_import
|
|
183
183
|
end
|
184
184
|
end
|
185
185
|
end
|
186
|
+
|
187
|
+
# If returning option is provided, it is only applied to top level models so that SQL with invalid
|
188
|
+
# columns, keys, etc isn't generated for child associations when doing recursive import
|
189
|
+
describe "returning" do
|
190
|
+
let(:new_topics) { Build(1, :topic_with_book) }
|
191
|
+
|
192
|
+
it "imports objects with associations" do
|
193
|
+
assert_difference "Topic.count", +1 do
|
194
|
+
Topic.import new_topics, recursive: true, returning: [:content], validate: false
|
195
|
+
new_topics.each do |topic|
|
196
|
+
assert_not_nil topic.id
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# If no returning option is provided, it is ignored
|
203
|
+
describe "no returning" do
|
204
|
+
let(:new_topics) { Build(1, :topic_with_book) }
|
205
|
+
|
206
|
+
it "is ignored and imports objects with associations" do
|
207
|
+
assert_difference "Topic.count", +1 do
|
208
|
+
Topic.import new_topics, recursive: true, no_returning: true, validate: false
|
209
|
+
new_topics.each do |topic|
|
210
|
+
assert_not_nil topic.id
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
186
215
|
end
|
187
216
|
end
|
@@ -5,21 +5,8 @@ def should_support_sqlite3_import_functionality
|
|
5
5
|
end
|
6
6
|
|
7
7
|
describe "#supports_imports?" do
|
8
|
-
|
9
|
-
|
10
|
-
version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.11")
|
11
|
-
assert ActiveRecord::Base.supports_import?(version)
|
12
|
-
|
13
|
-
version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.12")
|
14
|
-
assert ActiveRecord::Base.supports_import?(version)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
context "and SQLite less than 3.7.11" do
|
19
|
-
it "doesn't support import" do
|
20
|
-
version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new("3.7.10")
|
21
|
-
assert !ActiveRecord::Base.supports_import?(version)
|
22
|
-
end
|
8
|
+
it "should support import" do
|
9
|
+
assert ActiveRecord::Base.supports_import?
|
23
10
|
end
|
24
11
|
end
|
25
12
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-import
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -75,6 +75,8 @@ files:
|
|
75
75
|
- gemfiles/5.0.gemfile
|
76
76
|
- gemfiles/5.1.gemfile
|
77
77
|
- gemfiles/5.2.gemfile
|
78
|
+
- gemfiles/6.0.gemfile
|
79
|
+
- gemfiles/6.1.gemfile
|
78
80
|
- lib/activerecord-import.rb
|
79
81
|
- lib/activerecord-import/active_record/adapters/abstract_adapter.rb
|
80
82
|
- lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb
|
@@ -183,8 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
185
|
- !ruby/object:Gem::Version
|
184
186
|
version: '0'
|
185
187
|
requirements: []
|
186
|
-
|
187
|
-
rubygems_version: 2.6.11
|
188
|
+
rubygems_version: 3.0.6
|
188
189
|
signing_key:
|
189
190
|
specification_version: 4
|
190
191
|
summary: Bulk insert extension for ActiveRecord
|