activerecord-import 1.0.3 → 1.0.8

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
2
  SHA256:
3
- metadata.gz: b9bf4dc4ecf93b15a493cc34134158d7df3d6391b2e6c7fafbe79b630df48ea3
4
- data.tar.gz: ab5f7f4707598308dc9a2e7d4be5816f1d5674f63cea58e48b29f6ed87fa059a
3
+ metadata.gz: b32b91e70dc3ec25461a37092b774709300798912b930aeb9e7ae57dba41230d
4
+ data.tar.gz: 5b64d6bc14cae05785300a72cd830e552cd5b21d0dbb304e4e527f69de9865c5
5
5
  SHA512:
6
- metadata.gz: 05c4ad6281d6f0f574ee30cffed790763fb393fdf906ecc20bb8b4504f640ef9734ea1a43284f9821e543f8900aa3023bf66bcf455afcb2419e15828da618a05
7
- data.tar.gz: cdc67be0552c3b32a980598a784964df98549a27d428b89ef3ed7385acb4c75681946e010e714770ac81ff076088c0293e26bc04bbc2e15aecd42bf86c3aecc8
6
+ metadata.gz: 04a16537b5cd4ef535e7ed1c49397b1313d2717f5b60ed43cdee42f220d3a0094d0b4cce6241470c892a184b09d74b28ece67d28506508964f962c9282aa763c
7
+ data.tar.gz: d84f9ffea3a04a2ca3b2bb3dfc05307a90876d5c7e9f8e155605dbc7a6bbcec8cd3cfefc7aac82a7e14de0ae5334cdeba3ac6467c00206ccd15e016aebe8854e
data/.gitignore CHANGED
@@ -24,6 +24,7 @@ pkg
24
24
  log/*.log
25
25
  test.db
26
26
  test/database.yml
27
+ benchmarks/log/
27
28
 
28
29
  .ruby-*
29
30
  .bundle/
data/.travis.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
  rvm:
4
- - 2.5.5
4
+ - 2.5.8
5
5
 
6
6
  env:
7
7
  global:
@@ -11,6 +11,7 @@ env:
11
11
  - AR_VERSION=5.1
12
12
  - AR_VERSION=5.2
13
13
  - AR_VERSION=6.0
14
+ - AR_VERSION=6.1
14
15
 
15
16
  matrix:
16
17
  include:
@@ -28,7 +29,7 @@ matrix:
28
29
  fast_finish: true
29
30
 
30
31
  addons:
31
- postgresql: "9.5"
32
+ postgresql: "10"
32
33
  apt:
33
34
  sources:
34
35
  - travis-ci/sqlite3
@@ -37,13 +38,14 @@ addons:
37
38
  - sqlite3
38
39
  - mysql-server
39
40
  - mysql-client
40
- - postgresql-9.5-postgis-2.4
41
+ - postgresql-10-postgis-2.4
41
42
 
42
43
  before_install:
44
+ - sudo apt-get update
43
45
  - gem update --system
44
46
  - sudo mysql -e "use mysql; update user set authentication_string=PASSWORD('') where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;"
45
47
  - sudo mysql_upgrade
46
- - sudo service mysql restart
48
+ - sudo service mysql restart
47
49
 
48
50
  before_script:
49
51
  - mysql -e 'create database activerecord_import_test;'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,48 @@
1
+ ## Changes in 1.0.8
2
+
3
+ ### Fixes
4
+
5
+ * Use correct method for clearing query cache. Thanks to @EtienneDepaulis via \##719.
6
+
7
+ ## Changes in 1.0.7
8
+
9
+ * Use @@max_allowed_packet session variable instead of querying SHOW VARIABLES. Thanks to @diclophis via \#706.
10
+ * Add option :track_validation_failures. When this is set to true, failed_instances will be an array of arrays, with each inner array having the form [:index_in_dataset, :object_with_errors]. Thanks to @rorymckinley via \#684.
11
+
12
+ ### Fixes
13
+
14
+ * Prevent mass-assignment errors in Rails strict mode. Thanks to @diclophis via \##709.
15
+
16
+ ## Changes in 1.0.6
17
+
18
+ ### Fixes
19
+
20
+ * Handle after_initialize callbacks. Thanks to @AhMohsen46 via \#691 and
21
+ \#692.
22
+ * Fix regression introduced in 1.0.4. Explicity allow adapters to
23
+ support on duplicate key update. Thanks to @dsobiera, @jkowens via \#696.
24
+
25
+ ## Changes in 1.0.5
26
+
27
+ ### Fixes
28
+
29
+ * Allow serialized attributes to be returned from import. Thanks to @timanovsky, @jkowens via \#660.
30
+ * Return ActiveRecord::Connection from
31
+ ActiveREcord::Base#establish_connection. Thanks to @reverentF via
32
+ \#663.
33
+ * Support PostgreSQL array. Thanks to @ujihisa via \#669.
34
+ * Skip loading association ids when column changed. Thanks to @Aristat
35
+ via \#673.
36
+
37
+ ## Changes in 1.0.4
38
+
39
+ ### Fixes
40
+
41
+ * Use prepend pattern for ActiveRecord::Base#establish_connection patching. Thanks to @dombesz via \#648.
42
+ * Fix NoMethodError when using PostgreSQL ENUM types. Thanks to @sebcoetzee via \#651.
43
+ * Fix issue updating optimistic lock in Postgres. Thanks to @timanovsky
44
+ via \#656.
45
+
1
46
  ## Changes in 1.0.3
2
47
 
3
48
  ### New Features
@@ -253,7 +298,7 @@
253
298
  Thanks to @jkowens via \#301.
254
299
  * Allow for custom timestamp columns. Thanks to @mojidabckuu, @jkowens
255
300
  via \#401.
256
-
301
+
257
302
  ### Fixes
258
303
 
259
304
  * Fix ActiveRecord 5 issue coercing boolean values when serializing
@@ -265,7 +310,7 @@
265
310
 
266
311
  * Fix issue where PostgreSQL cannot recognize columns if names
267
312
  include mixed case characters. Thanks to @hugobgranja via \#379.
268
- * Fix an issue for ActiveRecord 5 where serialized fields with
313
+ * Fix an issue for ActiveRecord 5 where serialized fields with
269
314
  default values were not being typecast. Thanks to @whistlerbrk,
270
315
  @jkowens via \#386.
271
316
  * Add option :force_single_insert for MySQL to make sure a single
data/Gemfile CHANGED
@@ -6,8 +6,11 @@ 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
+ mysql2_version = '0.5.0' if version >= 6.1
9
10
  sqlite3_version = '1.3.0'
10
11
  sqlite3_version = '1.4.0' if version >= 6.0
12
+ pg_version = '0.9'
13
+ pg_version = '1.1' if version >= 6.1
11
14
 
12
15
  group :development, :test do
13
16
  gem 'rubocop', '~> 0.40.0'
@@ -17,7 +20,7 @@ end
17
20
  # Database Adapters
18
21
  platforms :ruby do
19
22
  gem "mysql2", "~> #{mysql2_version}"
20
- gem "pg", "~> 0.9"
23
+ gem "pg", "~> #{pg_version}"
21
24
  gem "sqlite3", "~> #{sqlite3_version}"
22
25
  gem "seamless_database_pool", "~> 1.0.20"
23
26
  end
@@ -47,6 +50,7 @@ end
47
50
 
48
51
  platforms :ruby do
49
52
  gem "pry-byebug"
53
+ gem "pry", "~> 0.12.0"
50
54
  gem "rb-readline"
51
55
  end
52
56
 
data/LICENSE CHANGED
@@ -1,56 +1,21 @@
1
- Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
2
- You can redistribute it and/or modify it under either the terms of the
3
- 2-clause BSDL (see the file BSDL), or the conditions below:
4
-
5
- 1. You may make and give away verbatim copies of the source form of the
6
- software without restriction, provided that you duplicate all of the
7
- original copyright notices and associated disclaimers.
8
-
9
- 2. You may modify your copy of the software in any way, provided that
10
- you do at least ONE of the following:
11
-
12
- a) place your modifications in the Public Domain or otherwise
13
- make them Freely Available, such as by posting said
14
- modifications to Usenet or an equivalent medium, or by allowing
15
- the author to include your modifications in the software.
16
-
17
- b) use the modified software only within your corporation or
18
- organization.
19
-
20
- c) give non-standard binaries non-standard names, with
21
- instructions on where to get the original software distribution.
22
-
23
- d) make other distribution arrangements with the author.
24
-
25
- 3. You may distribute the software in object code or binary form,
26
- provided that you do at least ONE of the following:
27
-
28
- a) distribute the binaries and library files of the software,
29
- together with instructions (in the manual page or equivalent)
30
- on where to get the original distribution.
31
-
32
- b) accompany the distribution with the machine-readable source of
33
- the software.
34
-
35
- c) give non-standard binaries non-standard names, with
36
- instructions on where to get the original software distribution.
37
-
38
- d) make other distribution arrangements with the author.
39
-
40
- 4. You may modify and include the part of the software into any other
41
- software (possibly commercial). But some files in the distribution
42
- are not written by the author, so that they are not under these terms.
43
-
44
- For the list of those files and their copying conditions, see the
45
- file LEGAL.
46
-
47
- 5. The scripts and library files supplied as input to or produced as
48
- output from the software do not automatically fall under the
49
- copyright of the software, but belong to whomever generated them,
50
- and may be sold commercially, and may be aggregated with this
51
- software.
52
-
53
- 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
- IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
- PURPOSE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Zach Dennis <zach.dennis@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.markdown CHANGED
@@ -60,6 +60,8 @@ The gem provides the following high-level features:
60
60
 
61
61
  #### Introduction
62
62
 
63
+ This gem adds an `import` method (or `bulk_import`, for compatibility with gems like `elasticsearch-model`; see [Conflicts With Other Gems](#conflicts-with-other-gems)) to ActiveRecord classes.
64
+
63
65
  Without `activerecord-import`, you'd write something like this:
64
66
 
65
67
  ```ruby
@@ -231,7 +233,8 @@ Book.import columns, books, batch_size: 2
231
233
 
232
234
  #### Recursive
233
235
 
234
- NOTE: This only works with PostgreSQL.
236
+ NOTE: This only works with PostgreSQL and ActiveRecord objects. This won't work with
237
+ hashes or arrays as recursive inputs.
235
238
 
236
239
  Assume that Books <code>has_many</code> Reviews.
237
240
 
@@ -247,19 +250,21 @@ Book.import books, recursive: true
247
250
 
248
251
  ### Options
249
252
 
250
- Key | Options | Default | Description
251
- ----------------------- | --------------------- | ------------------ | -----------
252
- :validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
253
- :validate_uniqueness | `true`/`false` | `false` | Whether or not to run uniqueness validations, has potential pitfalls, use with caution (requires `>= v0.27.0`).
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.
255
- :ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
256
- :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
- :synchronize | `Array` | N/A | An array of ActiveRecord instances. This synchronizes existing instances in memory with updates from the import.
258
- :timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
259
- :recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
260
- :batch_size | `Integer` | total # of records | Max number of records to insert per import
261
- :raise_error | `true`/`false` | `false` | Throws an exception if there are invalid records. `import!` is a shortcut for this.
262
-
253
+ Key | Options | Default | Description
254
+ ------------------------- | --------------------- | ------------------ | -----------
255
+ :validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
256
+ :validate_uniqueness | `true`/`false` | `false` | Whether or not to run uniqueness validations, has potential pitfalls, use with caution (requires `>= v0.27.0`).
257
+ :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.
258
+ :track_validation_failures| `true`/`false` | `false` | When this is set to true, `failed_instances` will be an array of arrays, with each inner array having the form `[:index_in_dataset, :object_with_errors]`
259
+ :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.
260
+ :ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
261
+ :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.
262
+ :synchronize | `Array` | N/A | An array of ActiveRecord instances. This synchronizes existing instances in memory with updates from the import.
263
+ :timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
264
+ :recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
265
+ :batch_size | `Integer` | total # of records | Max number of records to insert per import
266
+ :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.
267
+ :all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
263
268
 
264
269
  #### Duplicate Key Ignore
265
270
 
@@ -525,7 +530,7 @@ require 'activerecord-import'
525
530
  ### Load Path Setup
526
531
  To understand how rubygems loads code you can reference the following:
527
532
 
528
- http://guides.rubygems.org/patterns/#loading_code
533
+ http://guides.rubygems.org/patterns/#loading-code
529
534
 
530
535
  And an example of how active_record dynamically load adapters:
531
536
 
@@ -612,7 +617,7 @@ You can triage issues which may include reproducing bug reports or asking for vi
612
617
 
613
618
  # License
614
619
 
615
- This is licensed under the ruby license.
620
+ This is licensed under the MIT license.
616
621
 
617
622
  # Author
618
623
 
@@ -6,8 +6,8 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["zach.dennis@gmail.com"]
7
7
  gem.summary = "Bulk insert extension for ActiveRecord"
8
8
  gem.description = "A library for bulk inserting data using ActiveRecord."
9
- gem.homepage = "http://github.com/zdennis/activerecord-import"
10
- gem.license = "Ruby"
9
+ gem.homepage = "https://github.com/zdennis/activerecord-import"
10
+ gem.license = "MIT"
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
13
13
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = ActiveRecord::Import::VERSION
18
18
 
19
- gem.required_ruby_version = ">= 1.9.2"
19
+ gem.required_ruby_version = ">= 2.0.0"
20
20
 
21
21
  gem.add_runtime_dependency "activerecord", ">= 3.2"
22
22
  gem.add_development_dependency "rake"
data/gemfiles/6.0.gemfile CHANGED
@@ -1 +1,2 @@
1
1
  gem 'activerecord', '~> 6.0.0'
2
+ gem 'composite_primary_keys', '~> 12.0'
data/gemfiles/6.1.gemfile CHANGED
@@ -1 +1 @@
1
- gem 'activerecord', '~> 6.1.0.alpha', github: "rails/rails"
1
+ gem 'activerecord', '~> 6.1.0'
@@ -59,6 +59,12 @@ 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
  false
64
70
  end
@@ -56,9 +56,9 @@ module ActiveRecord::Import::MysqlAdapter
56
56
  # in a single packet
57
57
  def max_allowed_packet # :nodoc:
58
58
  @max_allowed_packet ||= begin
59
- result = execute( "SHOW VARIABLES like 'max_allowed_packet'" )
59
+ result = execute( "SELECT @@max_allowed_packet" )
60
60
  # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
61
- val = result.respond_to?(:fetch_row) ? result.fetch_row[1] : result.first[1]
61
+ val = result.respond_to?(:fetch_row) ? result.fetch_row[0] : result.first[0]
62
62
  val.to_i
63
63
  end
64
64
  end
@@ -102,7 +102,7 @@ module ActiveRecord::Import::MysqlAdapter
102
102
  qc = quote_column_name( column )
103
103
  "#{table_name}.#{qc}=VALUES(#{qc})"
104
104
  end
105
- increment_locking_column!(results, table_name, locking_column)
105
+ increment_locking_column!(table_name, results, locking_column)
106
106
  results.join( ',' )
107
107
  end
108
108
 
@@ -112,7 +112,7 @@ module ActiveRecord::Import::MysqlAdapter
112
112
  qc2 = quote_column_name( column2 )
113
113
  "#{table_name}.#{qc1}=VALUES( #{qc2} )"
114
114
  end
115
- increment_locking_column!(results, table_name, locking_column)
115
+ increment_locking_column!(table_name, results, locking_column)
116
116
  results.join( ',')
117
117
  end
118
118
 
@@ -121,9 +121,9 @@ module ActiveRecord::Import::MysqlAdapter
121
121
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
122
122
  end
123
123
 
124
- def increment_locking_column!(results, table_name, locking_column)
124
+ def increment_locking_column!(table_name, results, locking_column)
125
125
  if locking_column.present?
126
- results << "#{table_name}.`#{locking_column}`=`#{locking_column}`+1"
126
+ results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
127
127
  end
128
128
  end
129
129
  end
@@ -28,7 +28,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
28
28
  else
29
29
  select_values( sql2insert, *args )
30
30
  end
31
- query_cache.clear if query_cache_enabled
31
+ clear_query_cache if query_cache_enabled
32
32
  end
33
33
 
34
34
  if options[:returning].blank?
@@ -158,7 +158,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
158
158
  qc = quote_column_name( column )
159
159
  "#{qc}=EXCLUDED.#{qc}"
160
160
  end
161
- increment_locking_column!(results, locking_column)
161
+ increment_locking_column!(table_name, results, locking_column)
162
162
  results.join( ',' )
163
163
  end
164
164
 
@@ -168,7 +168,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
168
168
  qc2 = quote_column_name( column2 )
169
169
  "#{qc1}=EXCLUDED.#{qc2}"
170
170
  end
171
- increment_locking_column!(results, locking_column)
171
+ increment_locking_column!(table_name, results, locking_column)
172
172
  results.join( ',' )
173
173
  end
174
174
 
@@ -203,12 +203,6 @@ module ActiveRecord::Import::PostgreSQLAdapter
203
203
  true
204
204
  end
205
205
 
206
- def increment_locking_column!(results, locking_column)
207
- if locking_column.present?
208
- results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
209
- end
210
- end
211
-
212
206
  private
213
207
 
214
208
  def database_version
@@ -92,7 +92,7 @@ module ActiveRecord::Import::SQLite3Adapter
92
92
 
93
93
  # Returns a generated ON CONFLICT DO UPDATE statement given the passed
94
94
  # in +args+.
95
- def sql_for_on_duplicate_key_update( _table_name, *args ) # :nodoc:
95
+ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
96
96
  arg, primary_key, locking_column = args
97
97
  arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
98
98
  return unless arg.is_a?( Hash )
@@ -113,9 +113,9 @@ module ActiveRecord::Import::SQLite3Adapter
113
113
 
114
114
  sql << "#{conflict_target}DO UPDATE SET "
115
115
  if columns.is_a?( Array )
116
- sql << sql_for_on_duplicate_key_update_as_array( locking_column, columns )
116
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
117
117
  elsif columns.is_a?( Hash )
118
- sql << sql_for_on_duplicate_key_update_as_hash( locking_column, columns )
118
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
119
119
  elsif columns.is_a?( String )
120
120
  sql << columns
121
121
  else
@@ -127,22 +127,22 @@ module ActiveRecord::Import::SQLite3Adapter
127
127
  sql
128
128
  end
129
129
 
130
- def sql_for_on_duplicate_key_update_as_array( locking_column, arr ) # :nodoc:
130
+ def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
131
131
  results = arr.map do |column|
132
132
  qc = quote_column_name( column )
133
133
  "#{qc}=EXCLUDED.#{qc}"
134
134
  end
135
- increment_locking_column!(results, locking_column)
135
+ increment_locking_column!(table_name, results, locking_column)
136
136
  results.join( ',' )
137
137
  end
138
138
 
139
- def sql_for_on_duplicate_key_update_as_hash( locking_column, hsh ) # :nodoc:
139
+ def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
140
140
  results = hsh.map do |column1, column2|
141
141
  qc1 = quote_column_name( column1 )
142
142
  qc2 = quote_column_name( column2 )
143
143
  "#{qc1}=EXCLUDED.#{qc2}"
144
144
  end
145
- increment_locking_column!(results, locking_column)
145
+ increment_locking_column!(table_name, results, locking_column)
146
146
  results.join( ',' )
147
147
  end
148
148
 
@@ -166,12 +166,6 @@ module ActiveRecord::Import::SQLite3Adapter
166
166
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
167
167
  end
168
168
 
169
- def increment_locking_column!(results, locking_column)
170
- if locking_column.present?
171
- results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
172
- end
173
- end
174
-
175
169
  private
176
170
 
177
171
  def database_version
@@ -245,16 +245,17 @@ class ActiveRecord::Associations::CollectionAssociation
245
245
  alias import bulk_import unless respond_to? :import
246
246
  end
247
247
 
248
+ module ActiveRecord::Import::Connection
249
+ def establish_connection(args = nil)
250
+ conn = super(args)
251
+ ActiveRecord::Import.load_from_connection_pool connection_pool
252
+ conn
253
+ end
254
+ end
255
+
248
256
  class ActiveRecord::Base
249
257
  class << self
250
- def establish_connection_with_activerecord_import(*args)
251
- conn = establish_connection_without_activerecord_import(*args)
252
- ActiveRecord::Import.load_from_connection_pool connection_pool
253
- conn
254
- end
255
-
256
- alias establish_connection_without_activerecord_import establish_connection
257
- alias establish_connection establish_connection_with_activerecord_import
258
+ prepend ActiveRecord::Import::Connection
258
259
 
259
260
  # Returns true if the current database connection adapter
260
261
  # supports import functionality, otherwise returns false.
@@ -546,7 +547,7 @@ class ActiveRecord::Base
546
547
  alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
547
548
 
548
549
  def import_helper( *args )
549
- options = { validate: true, timestamps: true }
550
+ options = { validate: true, timestamps: true, track_validation_failures: false }
550
551
  options.merge!( args.pop ) if args.last.is_a? Hash
551
552
  # making sure that current model's primary key is used
552
553
  options[:primary_key] = primary_key
@@ -581,7 +582,7 @@ class ActiveRecord::Base
581
582
  if respond_to?(:timestamp_attributes_for_update, true)
582
583
  send(:timestamp_attributes_for_update).map(&:to_sym)
583
584
  else
584
- new.send(:timestamp_attributes_for_update_in_model)
585
+ allocate.send(:timestamp_attributes_for_update_in_model)
585
586
  end
586
587
  end
587
588
 
@@ -702,14 +703,18 @@ class ActiveRecord::Base
702
703
  # keep track of the instance and the position it is currently at. if this fails
703
704
  # validation we'll use the index to remove it from the array_of_attributes
704
705
  arr.each_with_index do |hsh, i|
705
- model = new
706
- hsh.each_pair { |k, v| model[k] = v }
706
+ # utilize block initializer syntax to prevent failure when 'mass_assignment_sanitizer = :strict'
707
+ model = new do |m|
708
+ hsh.each_pair { |k, v| m[k] = v }
709
+ end
710
+
707
711
  next if validator.valid_model?(model)
708
712
  raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
713
+
709
714
  array_of_attributes[i] = nil
710
715
  failure = model.dup
711
716
  failure.errors.send(:initialize_dup, model.errors)
712
- failed_instances << failure
717
+ failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
713
718
  end
714
719
  array_of_attributes.compact!
715
720
  end
@@ -807,7 +812,7 @@ class ActiveRecord::Base
807
812
  result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
808
813
  batch_values,
809
814
  options,
810
- "#{model_name} Create Many Without Validations Or Callbacks" )
815
+ "#{model_name} Create Many" )
811
816
  number_inserted += result.num_inserts
812
817
  ids += result.ids
813
818
  results += result.results
@@ -841,6 +846,19 @@ class ActiveRecord::Base
841
846
  end
842
847
  end
843
848
 
849
+ deserialize_value = lambda do |column, value|
850
+ column = columns_hash[column]
851
+ return value unless column
852
+ if respond_to?(:type_caster)
853
+ type = type_for_attribute(column.name)
854
+ type.deserialize(value)
855
+ elsif column.respond_to?(:type_cast_from_database)
856
+ column.type_cast_from_database(value)
857
+ else
858
+ value
859
+ end
860
+ end
861
+
844
862
  if models.size == import_result.results.size
845
863
  columns = Array(options[:returning])
846
864
  single_column = "#{columns.first}=" if columns.size == 1
@@ -848,10 +866,12 @@ class ActiveRecord::Base
848
866
  model = models[index]
849
867
 
850
868
  if single_column
851
- model.send(single_column, result)
869
+ val = deserialize_value.call(columns.first, result)
870
+ model.send(single_column, val)
852
871
  else
853
872
  columns.each_with_index do |column, col_index|
854
- model.send("#{column}=", result[col_index])
873
+ val = deserialize_value.call(column, result[col_index])
874
+ model.send("#{column}=", val)
855
875
  end
856
876
  end
857
877
  end
@@ -872,10 +892,12 @@ class ActiveRecord::Base
872
892
 
873
893
  # Sync belongs_to association ids with foreign key field
874
894
  def load_association_ids(model)
895
+ changed_columns = model.changed
875
896
  association_reflections = model.class.reflect_on_all_associations(:belongs_to)
876
897
  association_reflections.each do |association_reflection|
877
898
  column_name = association_reflection.foreign_key
878
899
  next if association_reflection.options[:polymorphic]
900
+ next if changed_columns.include?(column_name)
879
901
  association = model.association(association_reflection.name)
880
902
  association = association.target
881
903
  next if association.blank? || model.public_send(column_name).present?
@@ -955,7 +977,7 @@ class ActiveRecord::Base
955
977
  elsif column
956
978
  if respond_to?(:type_caster) # Rails 5.0 and higher
957
979
  type = type_for_attribute(column.name)
958
- val = type.type == :boolean ? type.cast(val) : type.serialize(val)
980
+ val = !type.respond_to?(:subtype) && type.type == :boolean ? type.cast(val) : type.serialize(val)
959
981
  connection_memo.quote(val)
960
982
  elsif column.respond_to?(:type_cast_from_user) # Rails 4.2
961
983
  connection_memo.quote(column.type_cast_from_user(val), column)
@@ -964,7 +986,7 @@ class ActiveRecord::Base
964
986
  val = serialized_attributes[column.name].dump(val)
965
987
  end
966
988
  # Fixes #443 to support binary (i.e. bytea) columns on PG
967
- val = column.type_cast(val) unless column.type.to_sym == :binary
989
+ val = column.type_cast(val) unless column.type && column.type.to_sym == :binary
968
990
  connection_memo.quote(val, column)
969
991
  end
970
992
  else
@@ -983,7 +1005,7 @@ class ActiveRecord::Base
983
1005
  timestamp_columns[:create] = timestamp_attributes_for_create_in_model
984
1006
  timestamp_columns[:update] = timestamp_attributes_for_update_in_model
985
1007
  else
986
- instance = new
1008
+ instance = allocate
987
1009
  timestamp_columns[:create] = instance.send(:timestamp_attributes_for_create_in_model)
988
1010
  timestamp_columns[:update] = instance.send(:timestamp_attributes_for_update_in_model)
989
1011
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/array'
2
+
1
3
  module ActiveRecord::Import
2
4
  class ValueSetTooLargeError < StandardError
3
5
  attr_reader :size
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "1.0.3".freeze
3
+ VERSION = "1.0.8".freeze
4
4
  end
5
5
  end
data/test/import_test.rb CHANGED
@@ -252,6 +252,16 @@ describe "#import" do
252
252
  end
253
253
  end
254
254
 
255
+ it "should index the failed instances by their poistion in the set if `track_failures` is true" do
256
+ index_offset = valid_values.length
257
+ results = Topic.import columns, valid_values + invalid_values, validate: true, track_validation_failures: true
258
+ assert_equal invalid_values.size, results.failed_instances.size
259
+ invalid_values.each_with_index do |value_set, index|
260
+ assert_equal index + index_offset, results.failed_instances[index].first
261
+ assert_equal value_set.first, results.failed_instances[index].last.title
262
+ end
263
+ end
264
+
255
265
  it "should set ids in valid models if adapter supports setting primary key of imported objects" do
256
266
  if ActiveRecord::Base.supports_setting_primary_key_of_imported_objects?
257
267
  Topic.import (invalid_models + valid_models), validate: true
@@ -900,4 +910,33 @@ describe "#import" do
900
910
  end
901
911
  end
902
912
  end
913
+ describe "importing model with after_initialize callback" do
914
+ let(:columns) { %w(name size) }
915
+ let(:valid_values) { [%w("Deer", "Small"), %w("Monkey", "Medium")] }
916
+ let(:invalid_values) do
917
+ [
918
+ { name: "giraffe", size: "Large" },
919
+ { size: "Medium" } # name is missing
920
+ ]
921
+ end
922
+ context "with validation checks turned off" do
923
+ it "should import valid data" do
924
+ Animal.import(columns, valid_values, validate: false)
925
+ assert_equal 2, Animal.count
926
+ end
927
+ it "should raise ArgumentError" do
928
+ assert_raise(ArgumentError) { Animal.import(invalid_values, validate: false) }
929
+ end
930
+ end
931
+
932
+ context "with validation checks turned on" do
933
+ it "should import valid data" do
934
+ Animal.import(columns, valid_values, validate: true)
935
+ assert_equal 2, Animal.count
936
+ end
937
+ it "should raise ArgumentError" do
938
+ assert_raise(ArgumentError) { Animal.import(invalid_values, validate: true) }
939
+ end
940
+ end
941
+ end
903
942
  end
@@ -0,0 +1,6 @@
1
+ class Animal < ActiveRecord::Base
2
+ after_initialize :validate_name_presence, if: :new_record?
3
+ def validate_name_presence
4
+ raise ArgumentError if name.nil?
5
+ end
6
+ end
@@ -3,8 +3,20 @@ 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
19
+ t.text :hours
8
20
  t.text :preferences
9
21
 
10
22
  if t.respond_to?(:json)
@@ -29,6 +41,8 @@ ActiveRecord::Schema.define do
29
41
  t.text :json_data
30
42
  end
31
43
 
44
+ t.column :vendor_type, :vendor_type
45
+
32
46
  t.datetime :created_at
33
47
  t.datetime :updated_at
34
48
  end
@@ -127,6 +127,15 @@ def should_support_postgresql_import_functionality
127
127
  end
128
128
  end
129
129
 
130
+ context "when a returning column is a serialized attribute" do
131
+ let(:vendor) { Vendor.new(hours: { monday: '8-5' }) }
132
+ let(:result) { Vendor.import([vendor], returning: %w(hours)) }
133
+
134
+ it "creates records" do
135
+ assert_difference("Vendor.count", +1) { result }
136
+ end
137
+ end
138
+
130
139
  context "when primary key and returning overlap" do
131
140
  let(:result) { Book.import(books, returning: %w(id title)) }
132
141
 
@@ -260,6 +269,17 @@ def should_support_postgresql_import_functionality
260
269
  end
261
270
  end
262
271
 
272
+ describe "with enum field" do
273
+ let(:vendor_type) { "retailer" }
274
+ it "imports the correct values for enum fields" do
275
+ vendor = Vendor.new(name: 'Vendor 1', vendor_type: vendor_type)
276
+ assert_difference "Vendor.count", +1 do
277
+ Vendor.import [vendor]
278
+ end
279
+ assert_equal(vendor_type, Vendor.first.vendor_type)
280
+ end
281
+ end
282
+
263
283
  describe "with binary field" do
264
284
  let(:binary_value) { "\xE0'c\xB2\xB0\xB3Bh\\\xC2M\xB1m\\I\xC4r".force_encoding('ASCII-8BIT') }
265
285
  it "imports the correct values for binary fields" 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
@@ -138,6 +138,15 @@ def should_support_recursive_import
138
138
  books.each do |book|
139
139
  assert_equal book.topic_id, second_new_topic.id
140
140
  end
141
+
142
+ books.each { |book| book.topic_id = nil }
143
+ assert_no_difference "Book.count", books.size do
144
+ Book.import books, validate: false, on_duplicate_key_update: [:topic_id]
145
+ end
146
+
147
+ books.each do |book|
148
+ assert_equal book.topic_id, nil
149
+ end
141
150
  end
142
151
 
143
152
  unless ENV["SKIP_COMPOSITE_PK"]
data/test/test_helper.rb CHANGED
@@ -48,7 +48,15 @@ adapter = ENV["ARE_DB"] || "sqlite3"
48
48
  FileUtils.mkdir_p 'log'
49
49
  ActiveRecord::Base.logger = Logger.new("log/test.log")
50
50
  ActiveRecord::Base.logger.level = Logger::DEBUG
51
- ActiveRecord::Base.configurations["test"] = YAML.load_file(test_dir.join("database.yml"))[adapter]
51
+
52
+ if ENV['AR_VERSION'].to_f >= 6.0
53
+ yaml_config = YAML.load_file(test_dir.join("database.yml"))[adapter]
54
+ config = ActiveRecord::DatabaseConfigurations::HashConfig.new("test", adapter, yaml_config)
55
+ ActiveRecord::Base.configurations.configurations << config
56
+ else
57
+ ActiveRecord::Base.configurations["test"] = YAML.load_file(test_dir.join("database.yml"))[adapter]
58
+ end
59
+
52
60
  ActiveRecord::Base.default_timezone = :utc
53
61
 
54
62
  require "activerecord-import"
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: 1.0.3
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Dennis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-09 00:00:00.000000000 Z
11
+ date: 2021-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -67,7 +67,7 @@ files:
67
67
  - benchmarks/models/test_innodb.rb
68
68
  - benchmarks/models/test_memory.rb
69
69
  - benchmarks/models/test_myisam.rb
70
- - benchmarks/schema/mysql_schema.rb
70
+ - benchmarks/schema/mysql2_schema.rb
71
71
  - gemfiles/3.2.gemfile
72
72
  - gemfiles/4.0.gemfile
73
73
  - gemfiles/4.1.gemfile
@@ -121,6 +121,7 @@ files:
121
121
  - test/makara_postgis/import_test.rb
122
122
  - test/models/account.rb
123
123
  - test/models/alarm.rb
124
+ - test/models/animal.rb
124
125
  - test/models/bike_maker.rb
125
126
  - test/models/book.rb
126
127
  - test/models/car.rb
@@ -166,11 +167,11 @@ files:
166
167
  - test/travis/database.yml
167
168
  - test/value_sets_bytes_parser_test.rb
168
169
  - test/value_sets_records_parser_test.rb
169
- homepage: http://github.com/zdennis/activerecord-import
170
+ homepage: https://github.com/zdennis/activerecord-import
170
171
  licenses:
171
- - Ruby
172
+ - MIT
172
173
  metadata: {}
173
- post_install_message:
174
+ post_install_message:
174
175
  rdoc_options: []
175
176
  require_paths:
176
177
  - lib
@@ -178,16 +179,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
179
  requirements:
179
180
  - - ">="
180
181
  - !ruby/object:Gem::Version
181
- version: 1.9.2
182
+ version: 2.0.0
182
183
  required_rubygems_version: !ruby/object:Gem::Requirement
183
184
  requirements:
184
185
  - - ">="
185
186
  - !ruby/object:Gem::Version
186
187
  version: '0'
187
188
  requirements: []
188
- rubyforge_project:
189
- rubygems_version: 2.7.7
190
- signing_key:
189
+ rubygems_version: 3.0.8
190
+ signing_key:
191
191
  specification_version: 4
192
192
  summary: Bulk insert extension for ActiveRecord
193
193
  test_files:
@@ -212,6 +212,7 @@ test_files:
212
212
  - test/makara_postgis/import_test.rb
213
213
  - test/models/account.rb
214
214
  - test/models/alarm.rb
215
+ - test/models/animal.rb
215
216
  - test/models/bike_maker.rb
216
217
  - test/models/book.rb
217
218
  - test/models/car.rb