activerecord-import 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbf4c01d6a3f043ed541f7331d6a19e651cbc035fcc3030b17f1e741a6d4e8b7
4
- data.tar.gz: a740491de16c78d4d94ccb428c69342fd8a809f235ce9d42fc41237a4d15c7e6
3
+ metadata.gz: 4c61743fafaad0de04ccf2c6bff4439fe66aed03e951e5c42e4f7926cf8dac39
4
+ data.tar.gz: c58d8992957e546b73bb7f7b90eaf2a741f007d2109a16db0e3b3d71b376e1da
5
5
  SHA512:
6
- metadata.gz: 48e62637f6493cd5446cd78aee3db3b4a58e646fb0bc07d3583a662f89f52b03c2a079c83fc2cae807c5577f922e1e3219354cf36dff8ae8cc81ae68266e711f
7
- data.tar.gz: 2f40f5fd61975589f8af932a3dbf5b6d14d2cfc6884ebe7019c85956c8c380d3faebffb1b600a7fa04536fe262ed40688946c8d35b8cf5b1bfedd1ddff20af5a
6
+ metadata.gz: 98638b63235eae1c16f27d3be932b8ce4e8f236b24350766792a4fc563d199cb1d048d25efa5d22abf3a21017fa07b9474c3168a4be6c1ba5aaa6e0843d584a3
7
+ data.tar.gz: 5c62bed2684f1b6d60d7e26b8ad13c6b7173a64835eeca9474082122425ab1f340f9063868224eda2539878210887701022e1ff6e6a37593872682bf90768d3a
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,47 @@
1
+ ## Changes in 1.1.0
2
+
3
+ ### New Features
4
+
5
+ * Add batch progress reporting. Thanks to @gee-forr via \##729.
6
+
7
+ ## Changes in 1.0.8
8
+
9
+ ### Fixes
10
+
11
+ * Use correct method for clearing query cache. Thanks to @EtienneDepaulis via \##719.
12
+
13
+ ## Changes in 1.0.7
14
+
15
+ ### New Features
16
+
17
+ * Use @@max_allowed_packet session variable instead of querying SHOW VARIABLES. Thanks to @diclophis via \#706.
18
+ * 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.
19
+
20
+ ### Fixes
21
+
22
+ * Prevent mass-assignment errors in Rails strict mode. Thanks to @diclophis via \##709.
23
+
24
+ ## Changes in 1.0.6
25
+
26
+ ### Fixes
27
+
28
+ * Handle after_initialize callbacks. Thanks to @AhMohsen46 via \#691 and
29
+ \#692.
30
+ * Fix regression introduced in 1.0.4. Explicity allow adapters to
31
+ support on duplicate key update. Thanks to @dsobiera, @jkowens via \#696.
32
+
33
+ ## Changes in 1.0.5
34
+
35
+ ### Fixes
36
+
37
+ * Allow serialized attributes to be returned from import. Thanks to @timanovsky, @jkowens via \#660.
38
+ * Return ActiveRecord::Connection from
39
+ ActiveREcord::Base#establish_connection. Thanks to @reverentF via
40
+ \#663.
41
+ * Support PostgreSQL array. Thanks to @ujihisa via \#669.
42
+ * Skip loading association ids when column changed. Thanks to @Aristat
43
+ via \#673.
44
+
1
45
  ## Changes in 1.0.4
2
46
 
3
47
  ### Fixes
@@ -262,7 +306,7 @@
262
306
  Thanks to @jkowens via \#301.
263
307
  * Allow for custom timestamp columns. Thanks to @mojidabckuu, @jkowens
264
308
  via \#401.
265
-
309
+
266
310
  ### Fixes
267
311
 
268
312
  * Fix ActiveRecord 5 issue coercing boolean values when serializing
@@ -274,7 +318,7 @@
274
318
 
275
319
  * Fix issue where PostgreSQL cannot recognize columns if names
276
320
  include mixed case characters. Thanks to @hugobgranja via \#379.
277
- * Fix an issue for ActiveRecord 5 where serialized fields with
321
+ * Fix an issue for ActiveRecord 5 where serialized fields with
278
322
  default values were not being typecast. Thanks to @whistlerbrk,
279
323
  @jkowens via \#386.
280
324
  * 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
@@ -229,9 +231,22 @@ columns = [ :title ]
229
231
  Book.import columns, books, batch_size: 2
230
232
  ```
231
233
 
234
+ If your import is particularly large or slow (possibly due to [callbacks](#callbacks)) whilst batch importing, you might want a way to report back on progress. This is supported by passing a callable as the `batch_progress` option. e.g:
235
+
236
+ ```ruby
237
+ my_proc = ->(rows_size, num_batches, current_batch_number, batch_duration_in_secs) {
238
+ # Using the arguments provided to the callable, you can
239
+ # send an email, post to a websocket,
240
+ # update slack, alert if import is taking too long, etc.
241
+ }
242
+
243
+ Book.import columns, books, batch_size: 2, batch_progress: my_proc
244
+ ```
245
+
232
246
  #### Recursive
233
247
 
234
- NOTE: This only works with PostgreSQL.
248
+ NOTE: This only works with PostgreSQL and ActiveRecord objects. This won't work with
249
+ hashes or arrays as recursive inputs.
235
250
 
236
251
  Assume that Books <code>has_many</code> Reviews.
237
252
 
@@ -247,20 +262,21 @@ Book.import books, recursive: true
247
262
 
248
263
  ### Options
249
264
 
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
- :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.
255
- :on_duplicate_key_ignore| `true`/`false` | `false` | Allows skipping records with duplicate keys. See [here](https://github.com/zdennis/activerecord-import/#duplicate-key-ignore) for more details.
256
- :ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
257
- :on_duplicate_key_update| :all, `Array`, `Hash` | N/A | Allows upsert logic to be used. See [here](https://github.com/zdennis/activerecord-import/#duplicate-key-update) for more details.
258
- :synchronize | `Array` | N/A | An array of ActiveRecord instances. This synchronizes existing instances in memory with updates from the import.
259
- :timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
260
- :recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
261
- :batch_size | `Integer` | total # of records | Max number of records to insert per import
262
- :raise_error | `true`/`false` | `false` | Raises an exception at the first invalid record. This means there will not be a result object returned. The `import!` method is a shortcut for this.
263
- :all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
265
+ Key | Options | Default | Description
266
+ ------------------------- | --------------------- | ------------------ | -----------
267
+ :validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
268
+ :validate_uniqueness | `true`/`false` | `false` | Whether or not to run uniqueness validations, has potential pitfalls, use with caution (requires `>= v0.27.0`).
269
+ :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.
270
+ :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]`
271
+ :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.
272
+ :ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
273
+ :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.
274
+ :synchronize | `Array` | N/A | An array of ActiveRecord instances. This synchronizes existing instances in memory with updates from the import.
275
+ :timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
276
+ :recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
277
+ :batch_size | `Integer` | total # of records | Max number of records to insert per import
278
+ :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.
279
+ :all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
264
280
 
265
281
  #### Duplicate Key Ignore
266
282
 
@@ -526,7 +542,7 @@ require 'activerecord-import'
526
542
  ### Load Path Setup
527
543
  To understand how rubygems loads code you can reference the following:
528
544
 
529
- http://guides.rubygems.org/patterns/#loading_code
545
+ http://guides.rubygems.org/patterns/#loading-code
530
546
 
531
547
  And an example of how active_record dynamically load adapters:
532
548
 
@@ -613,7 +629,7 @@ You can triage issues which may include reproducing bug reports or asking for vi
613
629
 
614
630
  # License
615
631
 
616
- This is licensed under the ruby license.
632
+ This is licensed under the MIT license.
617
633
 
618
634
  # Author
619
635
 
@@ -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'
@@ -66,7 +66,7 @@ module ActiveRecord::Import::AbstractAdapter
66
66
  end
67
67
 
68
68
  def supports_on_duplicate_key_update?
69
- true
69
+ false
70
70
  end
71
71
  end
72
72
  end
@@ -1,5 +1,6 @@
1
1
  module ActiveRecord::Import::MysqlAdapter
2
2
  include ActiveRecord::Import::ImportSupport
3
+ include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
3
4
 
4
5
  NO_MAX_PACKET = 0
5
6
  QUERY_OVERHEAD = 8 # This was shown to be true for MySQL, but it's not clear where the overhead is from.
@@ -55,9 +56,9 @@ module ActiveRecord::Import::MysqlAdapter
55
56
  # in a single packet
56
57
  def max_allowed_packet # :nodoc:
57
58
  @max_allowed_packet ||= begin
58
- result = execute( "SHOW VARIABLES like 'max_allowed_packet'" )
59
+ result = execute( "SELECT @@max_allowed_packet" )
59
60
  # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
60
- 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]
61
62
  val.to_i
62
63
  end
63
64
  end
@@ -1,5 +1,6 @@
1
1
  module ActiveRecord::Import::PostgreSQLAdapter
2
2
  include ActiveRecord::Import::ImportSupport
3
+ include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
3
4
 
4
5
  MIN_VERSION_FOR_UPSERT = 90_500
5
6
 
@@ -27,7 +28,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
27
28
  else
28
29
  select_values( sql2insert, *args )
29
30
  end
30
- query_cache.clear if query_cache_enabled
31
+ clear_query_cache if query_cache_enabled
31
32
  end
32
33
 
33
34
  if options[:returning].blank?
@@ -1,5 +1,6 @@
1
1
  module ActiveRecord::Import::SQLite3Adapter
2
2
  include ActiveRecord::Import::ImportSupport
3
+ include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
3
4
 
4
5
  MIN_VERSION_FOR_IMPORT = "3.7.11".freeze
5
6
  MIN_VERSION_FOR_UPSERT = "3.24.0".freeze
@@ -11,6 +11,12 @@ module ActiveRecord::Import #:nodoc:
11
11
  end
12
12
  end
13
13
 
14
+ module OnDuplicateKeyUpdateSupport #:nodoc:
15
+ def supports_on_duplicate_key_update? #:nodoc:
16
+ true
17
+ end
18
+ end
19
+
14
20
  class MissingColumnError < StandardError
15
21
  def initialize(name, index)
16
22
  super "Missing column for value <#{name}> at index #{index}"
@@ -241,8 +247,9 @@ end
241
247
 
242
248
  module ActiveRecord::Import::Connection
243
249
  def establish_connection(args = nil)
244
- super(args)
250
+ conn = super(args)
245
251
  ActiveRecord::Import.load_from_connection_pool connection_pool
252
+ conn
246
253
  end
247
254
  end
248
255
 
@@ -540,7 +547,7 @@ class ActiveRecord::Base
540
547
  alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
541
548
 
542
549
  def import_helper( *args )
543
- options = { validate: true, timestamps: true }
550
+ options = { validate: true, timestamps: true, track_validation_failures: false }
544
551
  options.merge!( args.pop ) if args.last.is_a? Hash
545
552
  # making sure that current model's primary key is used
546
553
  options[:primary_key] = primary_key
@@ -575,7 +582,7 @@ class ActiveRecord::Base
575
582
  if respond_to?(:timestamp_attributes_for_update, true)
576
583
  send(:timestamp_attributes_for_update).map(&:to_sym)
577
584
  else
578
- new.send(:timestamp_attributes_for_update_in_model)
585
+ allocate.send(:timestamp_attributes_for_update_in_model)
579
586
  end
580
587
  end
581
588
 
@@ -696,14 +703,18 @@ class ActiveRecord::Base
696
703
  # keep track of the instance and the position it is currently at. if this fails
697
704
  # validation we'll use the index to remove it from the array_of_attributes
698
705
  arr.each_with_index do |hsh, i|
699
- model = new
700
- 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
+
701
711
  next if validator.valid_model?(model)
702
712
  raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
713
+
703
714
  array_of_attributes[i] = nil
704
715
  failure = model.dup
705
716
  failure.errors.send(:initialize_dup, model.errors)
706
- failed_instances << failure
717
+ failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
707
718
  end
708
719
  array_of_attributes.compact!
709
720
  end
@@ -794,17 +805,29 @@ class ActiveRecord::Base
794
805
  if supports_import?
795
806
  # generate the sql
796
807
  post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
808
+ import_size = values_sql.size
809
+
810
+ batch_size = options[:batch_size] || import_size
811
+ run_proc = options[:batch_size].to_i.positive? && options[:batch_progress].respond_to?( :call )
812
+ progress_proc = options[:batch_progress]
813
+ current_batch = 0
814
+ batches = (import_size / batch_size.to_f).ceil
797
815
 
798
- batch_size = options[:batch_size] || values_sql.size
799
816
  values_sql.each_slice(batch_size) do |batch_values|
817
+ batch_started_at = Time.now.to_i
818
+
800
819
  # perform the inserts
801
820
  result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
802
821
  batch_values,
803
822
  options,
804
- "#{model_name} Create Many Without Validations Or Callbacks" )
823
+ "#{model_name} Create Many" )
824
+
805
825
  number_inserted += result.num_inserts
806
826
  ids += result.ids
807
827
  results += result.results
828
+ current_batch += 1
829
+
830
+ progress_proc.call(import_size, batches, current_batch, Time.now.to_i - batch_started_at) if run_proc
808
831
  end
809
832
  else
810
833
  transaction(requires_new: true) do
@@ -835,6 +858,19 @@ class ActiveRecord::Base
835
858
  end
836
859
  end
837
860
 
861
+ deserialize_value = lambda do |column, value|
862
+ column = columns_hash[column]
863
+ return value unless column
864
+ if respond_to?(:type_caster)
865
+ type = type_for_attribute(column.name)
866
+ type.deserialize(value)
867
+ elsif column.respond_to?(:type_cast_from_database)
868
+ column.type_cast_from_database(value)
869
+ else
870
+ value
871
+ end
872
+ end
873
+
838
874
  if models.size == import_result.results.size
839
875
  columns = Array(options[:returning])
840
876
  single_column = "#{columns.first}=" if columns.size == 1
@@ -842,10 +878,12 @@ class ActiveRecord::Base
842
878
  model = models[index]
843
879
 
844
880
  if single_column
845
- model.send(single_column, result)
881
+ val = deserialize_value.call(columns.first, result)
882
+ model.send(single_column, val)
846
883
  else
847
884
  columns.each_with_index do |column, col_index|
848
- model.send("#{column}=", result[col_index])
885
+ val = deserialize_value.call(column, result[col_index])
886
+ model.send("#{column}=", val)
849
887
  end
850
888
  end
851
889
  end
@@ -866,10 +904,12 @@ class ActiveRecord::Base
866
904
 
867
905
  # Sync belongs_to association ids with foreign key field
868
906
  def load_association_ids(model)
907
+ changed_columns = model.changed
869
908
  association_reflections = model.class.reflect_on_all_associations(:belongs_to)
870
909
  association_reflections.each do |association_reflection|
871
910
  column_name = association_reflection.foreign_key
872
911
  next if association_reflection.options[:polymorphic]
912
+ next if changed_columns.include?(column_name)
873
913
  association = model.association(association_reflection.name)
874
914
  association = association.target
875
915
  next if association.blank? || model.public_send(column_name).present?
@@ -949,7 +989,7 @@ class ActiveRecord::Base
949
989
  elsif column
950
990
  if respond_to?(:type_caster) # Rails 5.0 and higher
951
991
  type = type_for_attribute(column.name)
952
- val = type.type == :boolean ? type.cast(val) : type.serialize(val)
992
+ val = !type.respond_to?(:subtype) && type.type == :boolean ? type.cast(val) : type.serialize(val)
953
993
  connection_memo.quote(val)
954
994
  elsif column.respond_to?(:type_cast_from_user) # Rails 4.2
955
995
  connection_memo.quote(column.type_cast_from_user(val), column)
@@ -977,7 +1017,7 @@ class ActiveRecord::Base
977
1017
  timestamp_columns[:create] = timestamp_attributes_for_create_in_model
978
1018
  timestamp_columns[:update] = timestamp_attributes_for_update_in_model
979
1019
  else
980
- instance = new
1020
+ instance = allocate
981
1021
  timestamp_columns[:create] = instance.send(:timestamp_attributes_for_create_in_model)
982
1022
  timestamp_columns[:update] = instance.send(:timestamp_attributes_for_update_in_model)
983
1023
  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.4".freeze
3
+ VERSION = "1.1.0".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
@@ -395,6 +405,15 @@ describe "#import" do
395
405
  assert_equal 3, result.num_inserts if Topic.supports_import?
396
406
  end
397
407
  end
408
+
409
+ it "should accept and call an optional callable to run after each batch" do
410
+ lambda_called = 0
411
+
412
+ my_proc = ->(_row_count, _batches, _batch, _duration) { lambda_called += 1 }
413
+ Topic.import Build(10, :topics), batch_size: 4, batch_progress: my_proc
414
+
415
+ assert_equal 3, lambda_called
416
+ end
398
417
  end
399
418
 
400
419
  context "with :synchronize option" do
@@ -900,4 +919,33 @@ describe "#import" do
900
919
  end
901
920
  end
902
921
  end
922
+ describe "importing model with after_initialize callback" do
923
+ let(:columns) { %w(name size) }
924
+ let(:valid_values) { [%w("Deer", "Small"), %w("Monkey", "Medium")] }
925
+ let(:invalid_values) do
926
+ [
927
+ { name: "giraffe", size: "Large" },
928
+ { size: "Medium" } # name is missing
929
+ ]
930
+ end
931
+ context "with validation checks turned off" do
932
+ it "should import valid data" do
933
+ Animal.import(columns, valid_values, validate: false)
934
+ assert_equal 2, Animal.count
935
+ end
936
+ it "should raise ArgumentError" do
937
+ assert_raise(ArgumentError) { Animal.import(invalid_values, validate: false) }
938
+ end
939
+ end
940
+
941
+ context "with validation checks turned on" do
942
+ it "should import valid data" do
943
+ Animal.import(columns, valid_values, validate: true)
944
+ assert_equal 2, Animal.count
945
+ end
946
+ it "should raise ArgumentError" do
947
+ assert_raise(ArgumentError) { Animal.import(invalid_values, validate: true) }
948
+ end
949
+ end
950
+ end
903
951
  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
@@ -16,6 +16,7 @@ ActiveRecord::Schema.define do
16
16
 
17
17
  create_table :vendors, id: :uuid, force: :cascade do |t|
18
18
  t.string :name, null: true
19
+ t.text :hours
19
20
  t.text :preferences
20
21
 
21
22
  if t.respond_to?(:json)
@@ -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
 
@@ -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.4
4
+ version: 1.1.0
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-12-24 00:00:00.000000000 Z
11
+ date: 2021-05-15 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,15 +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
- rubygems_version: 3.0.6
189
- signing_key:
189
+ rubygems_version: 3.0.8
190
+ signing_key:
190
191
  specification_version: 4
191
192
  summary: Bulk insert extension for ActiveRecord
192
193
  test_files:
@@ -211,6 +212,7 @@ test_files:
211
212
  - test/makara_postgis/import_test.rb
212
213
  - test/models/account.rb
213
214
  - test/models/alarm.rb
215
+ - test/models/animal.rb
214
216
  - test/models/bike_maker.rb
215
217
  - test/models/book.rb
216
218
  - test/models/car.rb