activerecord-import 0.28.1 → 1.0.3
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 +66 -0
- data/Gemfile +3 -1
- data/README.markdown +47 -56
- data/gemfiles/6.0.gemfile +1 -0
- data/gemfiles/6.1.gemfile +1 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +1 -1
- data/lib/activerecord-import/adapters/mysql_adapter.rb +1 -4
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +11 -5
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +10 -8
- data/lib/activerecord-import/base.rb +8 -1
- data/lib/activerecord-import/import.rb +38 -36
- 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 +2 -0
- data/test/support/postgresql/import_examples.rb +30 -0
- data/test/support/shared_examples/recursive_import.rb +29 -0
- data/test/support/sqlite3/import_examples.rb +2 -15
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b9bf4dc4ecf93b15a493cc34134158d7df3d6391b2e6c7fafbe79b630df48ea3
|
4
|
+
data.tar.gz: ab5f7f4707598308dc9a2e7d4be5816f1d5674f63cea58e48b29f6ed87fa059a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05c4ad6281d6f0f574ee30cffed790763fb393fdf906ecc20bb8b4504f640ef9734ea1a43284f9821e543f8900aa3023bf66bcf455afcb2419e15828da618a05
|
7
|
+
data.tar.gz: cdc67be0552c3b32a980598a784964df98549a27d428b89ef3ed7385acb4c75681946e010e714770ac81ff076088c0293e26bc04bbc2e15aecd42bf86c3aecc8
|
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,69 @@
|
|
1
|
+
## Changes in 1.0.3
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
|
5
|
+
* Add support for ActiveRecord 6.1.0.alpha. Thanks to @imtayadeway via
|
6
|
+
\#642.
|
7
|
+
|
8
|
+
### Fixes
|
9
|
+
|
10
|
+
* Return an empty array for results instead of nil when importing empty
|
11
|
+
array. Thanks to @gyfis via \#636.
|
12
|
+
|
13
|
+
## Changes in 1.0.2
|
14
|
+
|
15
|
+
### New Features
|
16
|
+
|
17
|
+
* Add support for CockroachDB adapter. Thanks to @willie via \#605.
|
18
|
+
* Add support for ActiveRecord 6.0.0.rc1. Thanks to @madeindjs, @bill-filler,
|
19
|
+
@jkowens via \#619, \#623.
|
20
|
+
|
21
|
+
### Fixes
|
22
|
+
|
23
|
+
* Fixes NoMethodError when attempting to use nil logger. Thanks to @MattMecel,
|
24
|
+
@khiav22357.
|
25
|
+
* Fix issue validating STI models. Thanks to @thejbsmith, @jkowens via
|
26
|
+
\#626.
|
27
|
+
|
28
|
+
## Changes in 1.0.1
|
29
|
+
|
30
|
+
### Fixes
|
31
|
+
|
32
|
+
* Raise an error with a helpful message if array of values exceeds the number of
|
33
|
+
columns for a table. Thanks to @golddranks via \#589.
|
34
|
+
* Properly check if model responds to import before creating alias.
|
35
|
+
Thanks to @jcw- via \#591.
|
36
|
+
* No longer pass :returning option to child associations on recursive
|
37
|
+
import. Thanks to @dmitriy-kiriyenko via \#595.
|
38
|
+
* Fix import issue for models with Postgresql json/jsonb fields. Thanks
|
39
|
+
to @stokarenko via \#594.
|
40
|
+
* Fix issue importing models with timestamps that contain timezone
|
41
|
+
information. Thaks to @dekaikiwi, @jkowens via \#598.
|
42
|
+
* Ignore :no_returning when using :recursive option. Thanks to @dgollahon, @jkowens
|
43
|
+
via \#599.
|
44
|
+
|
45
|
+
## Changes in 1.0.0
|
46
|
+
|
47
|
+
### New Features
|
48
|
+
|
49
|
+
* Move ActiveRecord::Dirty changes to previous_changes after import.
|
50
|
+
Thanks to @stokarenko via \#584.
|
51
|
+
|
52
|
+
### Breaking Changes
|
53
|
+
|
54
|
+
* Previously :on_duplicate_key_update was enabled by default for MySQL.
|
55
|
+
The update timestamp columns (updated_at, updated_on) would be updated
|
56
|
+
on duplicate key. This was behavior is inconsistent with the other database
|
57
|
+
adapters and could also be considered surprising. Going forward it must
|
58
|
+
be explicitly enabled. See \#548.
|
59
|
+
|
60
|
+
## Changes in 0.28.2
|
61
|
+
|
62
|
+
### Fixes
|
63
|
+
|
64
|
+
* Fix issue where validations where not working in certain scenarios.
|
65
|
+
Thanks to @CASIXx1 via \#579.
|
66
|
+
|
1
67
|
## Changes in 0.28.1
|
2
68
|
|
3
69
|
### 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
has_many 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,7 +249,7 @@ 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`).
|
253
254
|
: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
255
|
:ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
|
@@ -267,14 +268,14 @@ Key | Options | Default | Descripti
|
|
267
268
|
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
269
|
|
269
270
|
```ruby
|
270
|
-
book = Book.create! title: "Book1", author: "
|
271
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
271
272
|
book.title = "Updated Book Title"
|
272
273
|
book.author = "Bob Barker"
|
273
274
|
|
274
275
|
Book.import [book], on_duplicate_key_ignore: true
|
275
276
|
|
276
277
|
book.reload.title # => "Book1" (stayed the same)
|
277
|
-
book.reload.author # => "
|
278
|
+
book.reload.author # => "George Orwell" (stayed the same)
|
278
279
|
```
|
279
280
|
|
280
281
|
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 +291,7 @@ This will use MySQL's `ON DUPLICATE KEY UPDATE` or Postgres/SQLite `ON CONFLICT
|
|
290
291
|
Basic Update
|
291
292
|
|
292
293
|
```ruby
|
293
|
-
book = Book.create! title: "Book1", author: "
|
294
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
294
295
|
book.title = "Updated Book Title"
|
295
296
|
book.author = "Bob Barker"
|
296
297
|
|
@@ -304,13 +305,13 @@ Book.import [book], on_duplicate_key_update: {conflict_target: [:id], columns: [
|
|
304
305
|
Book.import [book], on_duplicate_key_update: [:title]
|
305
306
|
|
306
307
|
book.reload.title # => "Updated Book Title" (changed)
|
307
|
-
book.reload.author # => "
|
308
|
+
book.reload.author # => "George Orwell" (stayed the same)
|
308
309
|
```
|
309
310
|
|
310
311
|
Using the value from another column
|
311
312
|
|
312
313
|
```ruby
|
313
|
-
book = Book.create! title: "Book1", author: "
|
314
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
314
315
|
book.title = "Updated Book Title"
|
315
316
|
|
316
317
|
# MySQL version
|
@@ -328,7 +329,7 @@ book.reload.author # => "Updated Book Title" (changed)
|
|
328
329
|
Using Custom SQL
|
329
330
|
|
330
331
|
```ruby
|
331
|
-
book = Book.create! title: "Book1", author: "
|
332
|
+
book = Book.create! title: "Book1", author: "George Orwell"
|
332
333
|
book.author = "Bob Barker"
|
333
334
|
|
334
335
|
# MySQL version
|
@@ -349,7 +350,7 @@ book.reload.author # => "Bob Barker" (changed)
|
|
349
350
|
PostgreSQL Using constraints
|
350
351
|
|
351
352
|
```ruby
|
352
|
-
book = Book.create! title: "Book1", author: "
|
353
|
+
book = Book.create! title: "Book1", author: "George Orwell", edition: 3, published_at: nil
|
353
354
|
book.published_at = Time.now
|
354
355
|
|
355
356
|
# in migration
|
@@ -363,7 +364,7 @@ Book.import [book], on_duplicate_key_update: {constraint_name: :for_upsert, colu
|
|
363
364
|
|
364
365
|
|
365
366
|
book.reload.title # => "Book1" (stayed the same)
|
366
|
-
book.reload.author # => "
|
367
|
+
book.reload.author # => "George Orwell" (stayed the same)
|
367
368
|
book.reload.edition # => 3 (stayed the same)
|
368
369
|
book.reload.published_at # => 2017-10-09 (changed)
|
369
370
|
```
|
@@ -383,7 +384,7 @@ articles = [
|
|
383
384
|
Article.new(author_id: 3, content: '')
|
384
385
|
]
|
385
386
|
|
386
|
-
demo = Article.import(articles
|
387
|
+
demo = Article.import(articles, returning: :title) # => #<struct ActiveRecord::Import::Result
|
387
388
|
|
388
389
|
demo.failed_instances
|
389
390
|
=> [#<Article id: 3, author_id: 3, title: nil, content: "", created_at: nil, updated_at: nil>]
|
@@ -397,7 +398,7 @@ demo.ids
|
|
397
398
|
|
398
399
|
demo.results
|
399
400
|
=> ["First Article", "Second Article"] # for Postgres
|
400
|
-
=> [] for other DBs
|
401
|
+
=> [] # for other DBs
|
401
402
|
```
|
402
403
|
|
403
404
|
### Counter Cache
|
@@ -552,7 +553,7 @@ When rubygems pushes the `lib` folder onto the load path a `require` will now fi
|
|
552
553
|
|
553
554
|
### Conflicts With Other Gems
|
554
555
|
|
555
|
-
|
556
|
+
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
557
|
|
557
558
|
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
559
|
|
@@ -579,7 +580,7 @@ See https://github.com/zdennis/activerecord-import/issues/233 for further discus
|
|
579
580
|
|
580
581
|
### More Information
|
581
582
|
|
582
|
-
For more information on
|
583
|
+
For more information on Activerecord-Import please see its wiki: https://github.com/zdennis/activerecord-import/wiki
|
583
584
|
|
584
585
|
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
586
|
|
@@ -605,6 +606,10 @@ AR_VERSION=4.2 bundle exec rake test:postgresql test:sqlite3 test:mysql2
|
|
605
606
|
|
606
607
|
Once you have pushed up your changes, you can find your CI results [here](https://travis-ci.org/zdennis/activerecord-import/).
|
607
608
|
|
609
|
+
## Issue Triage [![Open Source Helpers](https://www.codetriage.com/zdennis/activerecord-import/badges/users.svg)](https://www.codetriage.com/zdennis/activerecord-import)
|
610
|
+
|
611
|
+
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).
|
612
|
+
|
608
613
|
# License
|
609
614
|
|
610
615
|
This is licensed under the ruby license.
|
@@ -612,17 +617,3 @@ This is licensed under the ruby license.
|
|
612
617
|
# Author
|
613
618
|
|
614
619
|
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
|
|
@@ -71,14 +71,11 @@ module ActiveRecord::Import::MysqlAdapter
|
|
71
71
|
|
72
72
|
# Add a column to be updated on duplicate key update
|
73
73
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
74
|
-
if options
|
75
|
-
columns = options[:on_duplicate_key_update]
|
74
|
+
if (columns = options[:on_duplicate_key_update])
|
76
75
|
case columns
|
77
76
|
when Array then columns << column.to_sym unless columns.include?(column.to_sym)
|
78
77
|
when Hash then columns[column.to_sym] = column.to_sym
|
79
78
|
end
|
80
|
-
elsif !options[:ignore] && !options[:on_duplicate_key_ignore]
|
81
|
-
options[:on_duplicate_key_update] = [column.to_sym]
|
82
79
|
end
|
83
80
|
end
|
84
81
|
|
@@ -19,7 +19,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
19
19
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
20
20
|
|
21
21
|
columns = returning_columns(options)
|
22
|
-
if columns.blank? || options[:no_returning]
|
22
|
+
if columns.blank? || (options[:no_returning] && !options[:recursive])
|
23
23
|
insert( sql2insert, *args )
|
24
24
|
else
|
25
25
|
returned_values = if columns.size > 1
|
@@ -73,14 +73,14 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
73
73
|
if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
|
74
74
|
sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
|
75
75
|
end
|
76
|
-
elsif options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
76
|
+
elsif logger && options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
77
77
|
logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
|
78
78
|
end
|
79
79
|
|
80
80
|
sql += super(table_name, options)
|
81
81
|
|
82
82
|
columns = returning_columns(options)
|
83
|
-
unless columns.blank? || options[:no_returning]
|
83
|
+
unless columns.blank? || (options[:no_returning] && !options[:recursive])
|
84
84
|
sql << " RETURNING \"#{columns.join('", "')}\""
|
85
85
|
end
|
86
86
|
|
@@ -195,8 +195,8 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
195
195
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
|
196
196
|
end
|
197
197
|
|
198
|
-
def supports_on_duplicate_key_update?
|
199
|
-
|
198
|
+
def supports_on_duplicate_key_update?
|
199
|
+
database_version >= MIN_VERSION_FOR_UPSERT
|
200
200
|
end
|
201
201
|
|
202
202
|
def supports_setting_primary_key_of_imported_objects?
|
@@ -208,4 +208,10 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
208
208
|
results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
|
209
209
|
end
|
210
210
|
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def database_version
|
215
|
+
defined?(postgresql_version) ? postgresql_version : super
|
216
|
+
end
|
211
217
|
end
|
@@ -9,16 +9,12 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
9
9
|
# Override our conformance to ActiveRecord::Import::ImportSupport interface
|
10
10
|
# to ensure that we only support import in supported version of SQLite.
|
11
11
|
# 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
|
12
|
+
def supports_import?
|
13
|
+
database_version >= MIN_VERSION_FOR_IMPORT
|
18
14
|
end
|
19
15
|
|
20
|
-
def supports_on_duplicate_key_update?
|
21
|
-
|
16
|
+
def supports_on_duplicate_key_update?
|
17
|
+
database_version >= MIN_VERSION_FOR_UPSERT
|
22
18
|
end
|
23
19
|
|
24
20
|
# +sql+ can be a single string or an array. If it is an array all
|
@@ -175,4 +171,10 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
175
171
|
results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
|
176
172
|
end
|
177
173
|
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def database_version
|
178
|
+
defined?(sqlite_version) ? sqlite_version : super
|
179
|
+
end
|
178
180
|
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
|
|
@@ -26,44 +26,53 @@ module ActiveRecord::Import #:nodoc:
|
|
26
26
|
class Validator
|
27
27
|
def initialize(klass, options = {})
|
28
28
|
@options = options
|
29
|
+
@validator_class = klass
|
29
30
|
init_validations(klass)
|
30
31
|
end
|
31
32
|
|
32
33
|
def init_validations(klass)
|
33
34
|
@validate_callbacks = klass._validate_callbacks.dup
|
34
35
|
|
35
|
-
|
36
|
+
@validate_callbacks.each_with_index do |callback, i|
|
36
37
|
filter = callback.raw_filter
|
38
|
+
next unless filter.class.name =~ /Validations::PresenceValidator/ ||
|
39
|
+
(!@options[:validate_uniqueness] &&
|
40
|
+
filter.is_a?(ActiveRecord::Validations::UniquenessValidator))
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
callback = callback.dup
|
43
|
+
filter = filter.dup
|
44
|
+
attrs = filter.instance_variable_get(:@attributes).dup
|
45
|
+
|
46
|
+
if filter.is_a?(ActiveRecord::Validations::UniquenessValidator)
|
47
|
+
attrs = []
|
48
|
+
else
|
41
49
|
associations = klass.reflect_on_all_associations(:belongs_to)
|
42
|
-
attrs = filter.instance_variable_get(:@attributes).dup
|
43
50
|
associations.each do |assoc|
|
44
51
|
if (index = attrs.index(assoc.name))
|
45
52
|
key = assoc.foreign_key.to_sym
|
46
53
|
attrs[index] = key unless attrs.include?(key)
|
47
54
|
end
|
48
55
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
callback.filter = callback.send(:_compile_filter, filter)
|
58
|
-
@validate_callbacks[i] = callback
|
56
|
+
end
|
57
|
+
|
58
|
+
filter.instance_variable_set(:@attributes, attrs)
|
59
|
+
|
60
|
+
if @validate_callbacks.respond_to?(:chain, true)
|
61
|
+
@validate_callbacks.send(:chain).tap do |chain|
|
62
|
+
callback.instance_variable_set(:@filter, filter)
|
63
|
+
chain[i] = callback
|
59
64
|
end
|
60
|
-
|
61
|
-
|
65
|
+
else
|
66
|
+
callback.raw_filter = filter
|
67
|
+
callback.filter = callback.send(:_compile_filter, filter)
|
68
|
+
@validate_callbacks[i] = callback
|
62
69
|
end
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
66
73
|
def valid_model?(model)
|
74
|
+
init_validations(model.class) unless model.class == @validator_class
|
75
|
+
|
67
76
|
validation_context = @options[:validate_with_context]
|
68
77
|
validation_context ||= (model.new_record? ? :create : :update)
|
69
78
|
current_context = model.send(:validation_context)
|
@@ -522,7 +531,7 @@ class ActiveRecord::Base
|
|
522
531
|
import_helper(*args)
|
523
532
|
end
|
524
533
|
end
|
525
|
-
alias import bulk_import unless respond_to? :import
|
534
|
+
alias import bulk_import unless ActiveRecord::Base.respond_to? :import
|
526
535
|
|
527
536
|
# Imports a collection of values if all values are valid. Import fails at the
|
528
537
|
# first encountered validation error and raises ActiveRecord::RecordInvalid
|
@@ -534,7 +543,7 @@ class ActiveRecord::Base
|
|
534
543
|
|
535
544
|
bulk_import(*args, options)
|
536
545
|
end
|
537
|
-
alias import! bulk_import! unless respond_to? :import!
|
546
|
+
alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
|
538
547
|
|
539
548
|
def import_helper( *args )
|
540
549
|
options = { validate: true, timestamps: true }
|
@@ -568,15 +577,6 @@ class ActiveRecord::Base
|
|
568
577
|
end
|
569
578
|
end
|
570
579
|
|
571
|
-
default_values = column_defaults
|
572
|
-
stored_attrs = respond_to?(:stored_attributes) ? stored_attributes : {}
|
573
|
-
serialized_attrs = if defined?(ActiveRecord::Type::Serialized)
|
574
|
-
attrs = column_names.select { |c| type_for_attribute(c.to_s).class == ActiveRecord::Type::Serialized }
|
575
|
-
Hash[attrs.map { |a| [a, nil] }]
|
576
|
-
else
|
577
|
-
serialized_attributes
|
578
|
-
end
|
579
|
-
|
580
580
|
update_attrs = if record_timestamps && options[:timestamps]
|
581
581
|
if respond_to?(:timestamp_attributes_for_update, true)
|
582
582
|
send(:timestamp_attributes_for_update).map(&:to_sym)
|
@@ -602,12 +602,8 @@ class ActiveRecord::Base
|
|
602
602
|
update_attrs && update_attrs.include?(name.to_sym) &&
|
603
603
|
!model.send("#{name}_changed?")
|
604
604
|
nil
|
605
|
-
elsif stored_attrs.key?(name.to_sym) ||
|
606
|
-
serialized_attrs.key?(name.to_s) ||
|
607
|
-
default_values[name.to_s]
|
608
|
-
model.read_attribute(name.to_s)
|
609
605
|
else
|
610
|
-
model.
|
606
|
+
model.read_attribute(name.to_s)
|
611
607
|
end
|
612
608
|
end
|
613
609
|
end
|
@@ -634,7 +630,7 @@ class ActiveRecord::Base
|
|
634
630
|
end
|
635
631
|
# supports empty array
|
636
632
|
elsif args.last.is_a?( Array ) && args.last.empty?
|
637
|
-
return ActiveRecord::Import::Result.new([], 0, [])
|
633
|
+
return ActiveRecord::Import::Result.new([], 0, [], [])
|
638
634
|
# supports 2-element array and array
|
639
635
|
elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
|
640
636
|
|
@@ -862,7 +858,10 @@ class ActiveRecord::Base
|
|
862
858
|
end
|
863
859
|
|
864
860
|
models.each do |model|
|
865
|
-
if model.respond_to?(:
|
861
|
+
if model.respond_to?(:changes_applied) # Rails 4.1.8 and higher
|
862
|
+
model.changes_internally_applied if model.respond_to?(:changes_internally_applied) # legacy behavior for Rails 5.1
|
863
|
+
model.changes_applied
|
864
|
+
elsif model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
|
866
865
|
model.clear_changes_information
|
867
866
|
else # Rails 3.2
|
868
867
|
model.instance_variable_get(:@changed_attributes).clear
|
@@ -895,8 +894,9 @@ class ActiveRecord::Base
|
|
895
894
|
associated_objects_by_class = {}
|
896
895
|
models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
|
897
896
|
|
898
|
-
# :on_duplicate_key_update not supported for associations
|
897
|
+
# :on_duplicate_key_update and :returning not supported for associations
|
899
898
|
options.delete(:on_duplicate_key_update)
|
899
|
+
options.delete(:returning)
|
900
900
|
|
901
901
|
associated_objects_by_class.each_value do |associations|
|
902
902
|
associations.each_value do |associated_records|
|
@@ -967,6 +967,8 @@ class ActiveRecord::Base
|
|
967
967
|
val = column.type_cast(val) unless column.type.to_sym == :binary
|
968
968
|
connection_memo.quote(val, column)
|
969
969
|
end
|
970
|
+
else
|
971
|
+
raise ArgumentError, "Number of values (#{arr.length}) exceeds number of columns (#{columns.length})"
|
970
972
|
end
|
971
973
|
end
|
972
974
|
"(#{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 []
|
@@ -8,6 +8,7 @@ ActiveRecord::Schema.define do
|
|
8
8
|
t.text :preferences
|
9
9
|
|
10
10
|
if t.respond_to?(:json)
|
11
|
+
t.json :pure_json_data
|
11
12
|
t.json :data
|
12
13
|
else
|
13
14
|
t.text :data
|
@@ -20,6 +21,7 @@ ActiveRecord::Schema.define do
|
|
20
21
|
end
|
21
22
|
|
22
23
|
if t.respond_to?(:jsonb)
|
24
|
+
t.jsonb :pure_jsonb_data
|
23
25
|
t.jsonb :settings
|
24
26
|
t.jsonb :json_data, null: false, default: {}
|
25
27
|
else
|
@@ -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,19 @@ 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
|
231
261
|
end
|
232
262
|
|
233
263
|
describe "with binary field" 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.3
|
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-10-09 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
|
@@ -184,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
186
|
version: '0'
|
185
187
|
requirements: []
|
186
188
|
rubyforge_project:
|
187
|
-
rubygems_version: 2.
|
189
|
+
rubygems_version: 2.7.7
|
188
190
|
signing_key:
|
189
191
|
specification_version: 4
|
190
192
|
summary: Bulk insert extension for ActiveRecord
|