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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dbd7badbb2e98c6725ddaf5edbb713c4f79bfc01
4
- data.tar.gz: 0912df3959070820db94063d0172a43f4b027a9f
2
+ SHA256:
3
+ metadata.gz: b9bf4dc4ecf93b15a493cc34134158d7df3d6391b2e6c7fafbe79b630df48ea3
4
+ data.tar.gz: ab5f7f4707598308dc9a2e7d4be5816f1d5674f63cea58e48b29f6ed87fa059a
5
5
  SHA512:
6
- metadata.gz: 22fb70781c58df1795c41e5b8e954b8a89e6c85be0dbf33f2c2fb9265847778f99f414cbd7fe9ea945d6aa43f89d7557ea41541f938fa357a7bbcb2c0e6114df
7
- data.tar.gz: b480446464f626518ac128b2e79d4e4dbb2b50e8e52d6781812a0ee1594e5328430214ad03be9590c09dcfd07661ae54130b53ccde1b9b7d2131d2f40c80a633
6
+ metadata.gz: 05c4ad6281d6f0f574ee30cffed790763fb393fdf906ecc20bb8b4504f640ef9734ea1a43284f9821e543f8900aa3023bf66bcf455afcb2419e15828da618a05
7
+ data.tar.gz: cdc67be0552c3b32a980598a784964df98549a27d428b89ef3ed7385acb4c75681946e010e714770ac81ff076088c0293e26bc04bbc2e15aecd42bf86c3aecc8
@@ -1,30 +1,29 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
  rvm:
4
- - 2.3.7
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: jruby-9.1.14.0
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
- script:
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.3
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: trusty
68
+ dist: xenial
69
+
70
+ services:
71
+ - mysql
72
+ - postgresql
70
73
 
71
74
  sudo: required
@@ -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", "~> 1.3.10"
21
+ gem "sqlite3", "~> #{sqlite3_version}"
20
22
  gem "seamless_database_pool", "~> 1.0.20"
21
23
  end
22
24
 
@@ -1,6 +1,6 @@
1
- # activerecord-import [![Build Status](https://travis-ci.org/zdennis/activerecord-import.svg?branch=master)](https://travis-ci.org/zdennis/activerecord-import)
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
- activerecord-import is a library for bulk inserting data using ActiveRecord.
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
- * activerecord-import can work with raw columns and arrays of values (fastest)
27
- * activerecord-import works with model objects (faster)
28
- * activerecord-import can perform validations (fast)
29
- * activerecord-import can perform on duplicate key updates (requires MySQL or Postgres 9.5+)
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! :name => "book #{i}"
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(:name => "book #{i}")
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', 'FooManChu'], ['Book2', 'Bob Jones'] ]
89
+ values = [ ['Book1', 'George Orwell'], ['Book2', 'Bob Jones'] ]
89
90
 
90
91
  # Importing without model validations
91
- Book.import columns, values, :validate => false
92
+ Book.import columns, values, validate: false
92
93
 
93
94
  # Import with model validations
94
- Book.import columns, values, :validate => true
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: 'FooManChu' }, { title: 'Book2', author: 'Bob Jones'}]
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
- h2. Import Using Hashes and Explicit Column Names
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: "FooManChu" },
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(:title => "Book 1", :author => "FooManChu"),
175
- Book.new(:title => "Book 2", :author => "Bob Jones")
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, :validate => false
180
+ Book.import books, validate: false
180
181
 
181
182
  # with validations
182
- Book.import books, :validate => true
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(:title => "Book 1", :author => "FooManChu"),
193
- Book.new(:title => "Book 2", :author => "Bob Jones")
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, :validate => false
199
+ Book.import columns, books, validate: false
199
200
 
200
201
  # with validations
201
- Book.import columns, books, :validate => true
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(:title => "Book 1", :author => "FooManChu"),
221
- Book.new(:title => "Book 2", :author => "Bob Jones"),
222
- Book.new(:title => "Book 1", :author => "John Doe"),
223
- Book.new(:title => "Book 2", :author => "Richard Wright")
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, :batch_size => 2
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(:name => "book #{i}")
241
- book.reviews.build(:title => "Excellent")
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: "FooManChu"
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 # => "FooManChu" (stayed the same)
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: "FooManChu"
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 # => "FooManChu" (stayed the same)
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: "FooManChu"
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: "FooManChu"
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: "FooManChu", edition: 3, published_at: nil
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 # => "FooManChu" (stayed the same)
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), returning: :title # => #<struct ActiveRecord::Import::Result
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
- `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
+ 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 activerecord-import please see its wiki: https://github.com/zdennis/activerecord-import/wiki
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.include?(:on_duplicate_key_update)
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?(current_version = postgresql_version)
199
- current_version >= MIN_VERSION_FOR_UPSERT
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?(current_version = sqlite_version)
13
- if current_version >= MIN_VERSION_FOR_IMPORT
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?(current_version = sqlite_version)
21
- current_version >= MIN_VERSION_FOR_UPSERT
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
- require_adapter connection_pool.spec.config[:adapter]
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
- klass._validate_callbacks.each_with_index do |callback, i|
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
- if filter.class.name =~ /Validations::PresenceValidator/
39
- callback = callback.dup
40
- filter = filter.dup
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
- filter.instance_variable_set(:@attributes, attrs)
50
- if @validate_callbacks.respond_to?(:chain, true)
51
- @validate_callbacks.send(:chain).tap do |chain|
52
- callback.instance_variable_set(:@filter, filter)
53
- chain[i] = callback
54
- end
55
- else
56
- callback.raw_filter = filter
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
- elsif !@options[:validate_uniqueness] && filter.is_a?(ActiveRecord::Validations::UniquenessValidator)
61
- @validate_callbacks.delete(callback)
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.read_attribute_before_type_cast(name.to_s)
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?(:clear_changes_information) # Rails 4.0 and higher
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)
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.28.1".freeze
3
+ VERSION = "1.0.3".freeze
4
4
  end
5
5
  end
@@ -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
- context "and SQLite is 3.7.11 or higher" do
9
- it "supports import" do
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.28.1
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-01-04 00:00:00.000000000 Z
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.6.11
189
+ rubygems_version: 2.7.7
188
190
  signing_key:
189
191
  specification_version: 4
190
192
  summary: Bulk insert extension for ActiveRecord