activerecord-import 0.19.1 → 0.20.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/benchmarks/lib/cli_parser.rb +2 -1
- data/lib/activerecord-import/adapters/abstract_adapter.rb +1 -1
- data/lib/activerecord-import/adapters/mysql_adapter.rb +1 -1
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +44 -8
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +1 -1
- data/lib/activerecord-import/import.rb +68 -32
- data/lib/activerecord-import/version.rb +1 -1
- data/test/import_test.rb +6 -6
- data/test/models/topic.rb +8 -0
- data/test/schema/postgresql_schema.rb +1 -0
- data/test/support/factories.rb +1 -0
- data/test/support/mysql/import_examples.rb +2 -2
- data/test/support/postgresql/import_examples.rb +81 -0
- data/test/support/shared_examples/recursive_import.rb +13 -0
- data/test/value_sets_bytes_parser_test.rb +4 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4613a9ba2a76e71320a6e8107b9b557f2413212
|
4
|
+
data.tar.gz: 2fb7c7b7a5f801cd591e5cea93b088f0f6d52499
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72af6c0e217ef61e3bb87b1d078193d979f7d42e34dc5c721d5a26e72c50f1ce2b5a4a68808a2e4514b4a48135017c96cfef29e0dff4283fbc801d87b5039e18
|
7
|
+
data.tar.gz: 8b22f10f28ac4e0d7f7d66c4422abb6c4d7ff616a108ad5192cd4ba257e464c15b0dcb1f802c97a4f27bce1e6b57edaa48903d9ccfe68e75cd6c89ec0fa1229e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## Changes in 0.20.0
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
|
5
|
+
* Allow returning columns to be specified for PostgreSQL. Thanks to
|
6
|
+
@tjwp via \#433.
|
7
|
+
|
8
|
+
### Fixes
|
9
|
+
|
10
|
+
* Fixes an issue when bypassing uniqueness validators. Thanks to @vmaxv via \#444.
|
11
|
+
* For AR < 4.2, prevent type casting for binary columns on Postgresql. Thanks to @mwalsher via \#446.
|
12
|
+
* Fix issue logging class name on import. Thanks to @sophylee, @jkowens via \#447.
|
13
|
+
* Copy belongs_to association id to foreign key column before importing. Thanks to @jkowens via \#448.
|
14
|
+
* Reset model instance on validate. Thanks to @vmaxv via \#449.
|
15
|
+
|
1
16
|
## Changes in 0.19.1
|
2
17
|
|
3
18
|
### Fixes
|
data/Gemfile
CHANGED
@@ -16,7 +16,7 @@ module ActiveRecord::Import::AbstractAdapter
|
|
16
16
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
17
17
|
insert( sql2insert, *args )
|
18
18
|
|
19
|
-
[number_of_inserts, []]
|
19
|
+
ActiveRecord::Import::Result.new([], number_of_inserts, [], [])
|
20
20
|
end
|
21
21
|
|
22
22
|
def pre_sql_statements(options)
|
@@ -5,9 +5,10 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
5
5
|
MIN_VERSION_FOR_UPSERT = 90_500
|
6
6
|
|
7
7
|
def insert_many( sql, values, options = {}, *args ) # :nodoc:
|
8
|
-
primary_key = options[:primary_key]
|
9
8
|
number_of_inserts = 1
|
9
|
+
returned_values = []
|
10
10
|
ids = []
|
11
|
+
results = []
|
11
12
|
|
12
13
|
base_sql, post_sql = if sql.is_a?( String )
|
13
14
|
[sql, '']
|
@@ -17,11 +18,12 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
17
18
|
|
18
19
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
19
20
|
|
20
|
-
|
21
|
+
columns = returning_columns(options)
|
22
|
+
if columns.blank? || options[:no_returning]
|
21
23
|
insert( sql2insert, *args )
|
22
24
|
else
|
23
|
-
|
24
|
-
# Select composite
|
25
|
+
returned_values = if columns.size > 1
|
26
|
+
# Select composite columns
|
25
27
|
select_rows( sql2insert, *args )
|
26
28
|
else
|
27
29
|
select_values( sql2insert, *args )
|
@@ -29,7 +31,34 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
29
31
|
query_cache.clear if query_cache_enabled
|
30
32
|
end
|
31
33
|
|
32
|
-
[
|
34
|
+
if options[:returning].blank?
|
35
|
+
ids = returned_values
|
36
|
+
elsif options[:primary_key].blank?
|
37
|
+
results = returned_values
|
38
|
+
else
|
39
|
+
# split primary key and returning columns
|
40
|
+
ids, results = split_ids_and_results(returned_values, columns, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
|
44
|
+
end
|
45
|
+
|
46
|
+
def split_ids_and_results(values, columns, options)
|
47
|
+
ids = []
|
48
|
+
results = []
|
49
|
+
id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
|
50
|
+
returning_indexes = Array(options[:returning]).map { |key| columns.index(key) }
|
51
|
+
|
52
|
+
values.each do |value|
|
53
|
+
value_array = Array(value)
|
54
|
+
ids << id_indexes.map { |i| value_array[i] }
|
55
|
+
results << returning_indexes.map { |i| value_array[i] }
|
56
|
+
end
|
57
|
+
|
58
|
+
ids.map!(&:first) if id_indexes.size == 1
|
59
|
+
results.map!(&:first) if returning_indexes.size == 1
|
60
|
+
|
61
|
+
[ids, results]
|
33
62
|
end
|
34
63
|
|
35
64
|
def next_value_for_sequence(sequence_name)
|
@@ -50,14 +79,21 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
50
79
|
|
51
80
|
sql += super(table_name, options)
|
52
81
|
|
53
|
-
|
54
|
-
|
55
|
-
sql << " RETURNING \"#{
|
82
|
+
columns = returning_columns(options)
|
83
|
+
unless columns.blank? || options[:no_returning]
|
84
|
+
sql << " RETURNING \"#{columns.join('", "')}\""
|
56
85
|
end
|
57
86
|
|
58
87
|
sql
|
59
88
|
end
|
60
89
|
|
90
|
+
def returning_columns(options)
|
91
|
+
columns = []
|
92
|
+
columns += Array(options[:primary_key]) if options[:primary_key].present?
|
93
|
+
columns |= Array(options[:returning]) if options[:returning].present?
|
94
|
+
columns
|
95
|
+
end
|
96
|
+
|
61
97
|
# Add a column to be updated on duplicate key update
|
62
98
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
63
99
|
arg = options[:on_duplicate_key_update]
|
@@ -3,7 +3,7 @@ require "ostruct"
|
|
3
3
|
module ActiveRecord::Import::ConnectionAdapters; end
|
4
4
|
|
5
5
|
module ActiveRecord::Import #:nodoc:
|
6
|
-
Result = Struct.new(:failed_instances, :num_inserts, :ids)
|
6
|
+
Result = Struct.new(:failed_instances, :num_inserts, :ids, :results)
|
7
7
|
|
8
8
|
module ImportSupport #:nodoc:
|
9
9
|
def supports_import? #:nodoc:
|
@@ -38,7 +38,7 @@ module ActiveRecord::Import #:nodoc:
|
|
38
38
|
model.errors.clear
|
39
39
|
|
40
40
|
validate_callbacks = model._validate_callbacks.dup
|
41
|
-
|
41
|
+
model._validate_callbacks.each do |callback|
|
42
42
|
validate_callbacks.delete(callback) if callback.raw_filter.is_a? ActiveRecord::Validations::UniquenessValidator
|
43
43
|
end
|
44
44
|
|
@@ -214,18 +214,18 @@ class ActiveRecord::Base
|
|
214
214
|
#
|
215
215
|
# == Options
|
216
216
|
# * +validate+ - true|false, tells import whether or not to use
|
217
|
-
#
|
217
|
+
# ActiveRecord validations. Validations are enforced by default.
|
218
218
|
# * +ignore+ - true|false, an alias for on_duplicate_key_ignore.
|
219
219
|
# * +on_duplicate_key_ignore+ - true|false, tells import to discard
|
220
|
-
#
|
221
|
-
#
|
222
|
-
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
220
|
+
# records that contain duplicate keys. For Postgres 9.5+ it adds
|
221
|
+
# ON CONFLICT DO NOTHING, for MySQL it uses INSERT IGNORE, and for
|
222
|
+
# SQLite it uses INSERT OR IGNORE. Cannot be enabled on a
|
223
|
+
# recursive import. For database adapters that normally support
|
224
|
+
# setting primary keys on imported objects, this option prevents
|
225
|
+
# that from occurring.
|
226
226
|
# * +on_duplicate_key_update+ - an Array or Hash, tells import to
|
227
|
-
#
|
228
|
-
#
|
227
|
+
# use MySQL's ON DUPLICATE KEY UPDATE or Postgres 9.5+ ON CONFLICT
|
228
|
+
# DO UPDATE ability. See On Duplicate Key Update below.
|
229
229
|
# * +synchronize+ - an array of ActiveRecord instances for the model
|
230
230
|
# that you are currently importing data into. This synchronizes
|
231
231
|
# existing model instances in memory with updates from the import.
|
@@ -323,7 +323,7 @@ class ActiveRecord::Base
|
|
323
323
|
# conflicting constraint to be explicitly specified. Using this option
|
324
324
|
# allows you to specify a constraint other than the primary key.
|
325
325
|
#
|
326
|
-
#
|
326
|
+
# ===== :conflict_target
|
327
327
|
#
|
328
328
|
# The :conflict_target attribute specifies the columns that make up the
|
329
329
|
# conflicting unique constraint and can be a single column or an array of
|
@@ -333,7 +333,7 @@ class ActiveRecord::Base
|
|
333
333
|
#
|
334
334
|
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: [ :author_id, :slug ], columns: [ :date_modified ] }
|
335
335
|
#
|
336
|
-
#
|
336
|
+
# ===== :index_predicate
|
337
337
|
#
|
338
338
|
# The :index_predicate attribute optionally specifies a WHERE condition
|
339
339
|
# on :conflict_target, which is required for matching against partial
|
@@ -342,7 +342,7 @@ class ActiveRecord::Base
|
|
342
342
|
#
|
343
343
|
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: [ :author_id, :slug ], index_predicate: 'status <> 0', columns: [ :date_modified ] }
|
344
344
|
#
|
345
|
-
#
|
345
|
+
# ===== :constraint_name
|
346
346
|
#
|
347
347
|
# The :constraint_name attribute explicitly identifies the conflicting
|
348
348
|
# unique index by name. Postgres documentation discourages using this method
|
@@ -350,7 +350,7 @@ class ActiveRecord::Base
|
|
350
350
|
#
|
351
351
|
# BlogPost.import columns, values, on_duplicate_key_update: { constraint_name: :blog_posts_pkey, columns: [ :date_modified ] }
|
352
352
|
#
|
353
|
-
#
|
353
|
+
# ===== :condition
|
354
354
|
#
|
355
355
|
# The :condition attribute optionally specifies a WHERE condition
|
356
356
|
# on :conflict_action. Only rows for which this expression returns true will be updated.
|
@@ -358,12 +358,12 @@ class ActiveRecord::Base
|
|
358
358
|
# Below is an example:
|
359
359
|
#
|
360
360
|
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: [ :author_id ], condition: "blog_posts.title NOT LIKE '%sample%'", columns: [ :author_name ] }
|
361
|
-
|
362
|
-
#
|
361
|
+
#
|
362
|
+
# ===== :columns
|
363
363
|
#
|
364
364
|
# The :columns attribute can be either an Array or a Hash.
|
365
365
|
#
|
366
|
-
#
|
366
|
+
# ===== Using an Array
|
367
367
|
#
|
368
368
|
# The :columns attribute can be an array of column names. The column names
|
369
369
|
# are the only fields that are updated if a duplicate record is found.
|
@@ -371,7 +371,7 @@ class ActiveRecord::Base
|
|
371
371
|
#
|
372
372
|
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: :slug, columns: [ :date_modified, :content, :author ] }
|
373
373
|
#
|
374
|
-
#
|
374
|
+
# ===== Using a Hash
|
375
375
|
#
|
376
376
|
# The :columns option can be a hash of column names to model attribute name
|
377
377
|
# mappings. This gives you finer grained control over what fields are updated
|
@@ -384,6 +384,7 @@ class ActiveRecord::Base
|
|
384
384
|
# * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
|
385
385
|
# * num_inserts - the number of insert statements it took to import the data
|
386
386
|
# * ids - the primary keys of the imported ids if the adapter supports it, otherwise an empty array.
|
387
|
+
# * results - import results if the adapter supports it, otherwise an empty array.
|
387
388
|
def import(*args)
|
388
389
|
if args.first.is_a?( Array ) && args.first.first.is_a?(ActiveRecord::Base)
|
389
390
|
options = {}
|
@@ -445,6 +446,10 @@ class ActiveRecord::Base
|
|
445
446
|
end
|
446
447
|
|
447
448
|
array_of_attributes = models.map do |model|
|
449
|
+
if support_setting_primary_key_of_imported_objects?
|
450
|
+
load_association_ids(model)
|
451
|
+
end
|
452
|
+
|
448
453
|
column_names.map do |name|
|
449
454
|
if stored_attrs.key?(name.to_sym) ||
|
450
455
|
serialized_attrs.key?(name) ||
|
@@ -525,8 +530,7 @@ class ActiveRecord::Base
|
|
525
530
|
import_with_validations( column_names, array_of_attributes, options )
|
526
531
|
end
|
527
532
|
else
|
528
|
-
|
529
|
-
ActiveRecord::Import::Result.new([], num_inserts, ids)
|
533
|
+
import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
530
534
|
end
|
531
535
|
|
532
536
|
if options[:synchronize]
|
@@ -537,7 +541,7 @@ class ActiveRecord::Base
|
|
537
541
|
|
538
542
|
# if we have ids, then set the id on the models and mark the models as clean.
|
539
543
|
if models && support_setting_primary_key_of_imported_objects?
|
540
|
-
set_attributes_and_mark_clean(models, return_obj, timestamps)
|
544
|
+
set_attributes_and_mark_clean(models, return_obj, timestamps, options)
|
541
545
|
|
542
546
|
# if there are auto-save associations on the models we imported that are new, import them as well
|
543
547
|
import_associations(models, options.dup) if options[:recursive]
|
@@ -566,8 +570,8 @@ class ActiveRecord::Base
|
|
566
570
|
|
567
571
|
# keep track of the instance and the position it is currently at. if this fails
|
568
572
|
# validation we'll use the index to remove it from the array_of_attributes
|
569
|
-
model = new
|
570
573
|
arr.each_with_index do |hsh, i|
|
574
|
+
model = new
|
571
575
|
hsh.each_pair { |k, v| model[k] = v }
|
572
576
|
next if validator.valid_model? model
|
573
577
|
raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
|
@@ -580,12 +584,12 @@ class ActiveRecord::Base
|
|
580
584
|
|
581
585
|
array_of_attributes.compact!
|
582
586
|
|
583
|
-
|
584
|
-
[0, []]
|
587
|
+
result = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
|
588
|
+
ActiveRecord::Import::Result.new([], 0, [], [])
|
585
589
|
else
|
586
590
|
import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
587
591
|
end
|
588
|
-
ActiveRecord::Import::Result.new(failed_instances, num_inserts, ids)
|
592
|
+
ActiveRecord::Import::Result.new(failed_instances, result.num_inserts, result.ids, result.results)
|
589
593
|
end
|
590
594
|
|
591
595
|
# Imports the passed in +column_names+ and +array_of_attributes+
|
@@ -627,6 +631,7 @@ class ActiveRecord::Base
|
|
627
631
|
|
628
632
|
number_inserted = 0
|
629
633
|
ids = []
|
634
|
+
results = []
|
630
635
|
if supports_import?
|
631
636
|
# generate the sql
|
632
637
|
post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
|
@@ -637,9 +642,10 @@ class ActiveRecord::Base
|
|
637
642
|
result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
|
638
643
|
batch_values,
|
639
644
|
options,
|
640
|
-
"#{
|
641
|
-
number_inserted += result
|
642
|
-
ids += result
|
645
|
+
"#{model_name} Create Many Without Validations Or Callbacks" )
|
646
|
+
number_inserted += result.num_inserts
|
647
|
+
ids += result.ids
|
648
|
+
results += result.results
|
643
649
|
end
|
644
650
|
else
|
645
651
|
transaction(requires_new: true) do
|
@@ -649,12 +655,12 @@ class ActiveRecord::Base
|
|
649
655
|
end
|
650
656
|
end
|
651
657
|
end
|
652
|
-
[number_inserted, ids
|
658
|
+
ActiveRecord::Import::Result.new([], number_inserted, ids, results)
|
653
659
|
end
|
654
660
|
|
655
661
|
private
|
656
662
|
|
657
|
-
def set_attributes_and_mark_clean(models, import_result, timestamps)
|
663
|
+
def set_attributes_and_mark_clean(models, import_result, timestamps, options)
|
658
664
|
return if models.nil?
|
659
665
|
models -= import_result.failed_instances
|
660
666
|
|
@@ -670,6 +676,22 @@ class ActiveRecord::Base
|
|
670
676
|
end
|
671
677
|
end
|
672
678
|
|
679
|
+
if models.size == import_result.results.size
|
680
|
+
columns = Array(options[:returning])
|
681
|
+
single_column = "#{columns.first}=" if columns.size == 1
|
682
|
+
import_result.results.each_with_index do |result, index|
|
683
|
+
model = models[index]
|
684
|
+
|
685
|
+
if single_column
|
686
|
+
model.send(single_column, result)
|
687
|
+
else
|
688
|
+
columns.each_with_index do |column, col_index|
|
689
|
+
model.send("#{column}=", result[col_index])
|
690
|
+
end
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
673
695
|
models.each do |model|
|
674
696
|
if model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
|
675
697
|
model.clear_changes_information
|
@@ -680,6 +702,18 @@ class ActiveRecord::Base
|
|
680
702
|
end
|
681
703
|
end
|
682
704
|
|
705
|
+
# Sync belongs_to association ids with foreign key field
|
706
|
+
def load_association_ids(model)
|
707
|
+
association_reflections = model.class.reflect_on_all_associations(:belongs_to)
|
708
|
+
association_reflections.each do |association_reflection|
|
709
|
+
association = model.association(association_reflection.name)
|
710
|
+
association = association.target
|
711
|
+
if association && association.id
|
712
|
+
model.public_send("#{association_reflection.foreign_key}=", association.id)
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
683
717
|
def import_associations(models, options)
|
684
718
|
# now, for all the dirty associations, collect them into a new set of models, then recurse.
|
685
719
|
# notes:
|
@@ -754,7 +788,9 @@ class ActiveRecord::Base
|
|
754
788
|
if serialized_attributes.include?(column.name)
|
755
789
|
val = serialized_attributes[column.name].dump(val)
|
756
790
|
end
|
757
|
-
|
791
|
+
# Fixes #443 to support binary (i.e. bytea) columns on PG
|
792
|
+
val = column.type_cast(val) unless column.type.to_sym == :binary
|
793
|
+
connection_memo.quote(val, column)
|
758
794
|
end
|
759
795
|
end
|
760
796
|
end
|
data/test/import_test.rb
CHANGED
@@ -121,12 +121,12 @@ describe "#import" do
|
|
121
121
|
end
|
122
122
|
|
123
123
|
context "with :validation option" do
|
124
|
-
let(:columns) { %w(title author_name) }
|
125
|
-
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
126
|
-
let(:valid_values_with_context) { [[1111, "Jerry Carter"], [2222, "Chad Fowler"]] }
|
127
|
-
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
128
|
-
let(:valid_models) { valid_values.map { |title, author_name| Topic.new(title: title, author_name: author_name) } }
|
129
|
-
let(:invalid_models) { invalid_values.map { |title, author_name| Topic.new(title: title, author_name: author_name) } }
|
124
|
+
let(:columns) { %w(title author_name content) }
|
125
|
+
let(:valid_values) { [["LDAP", "Jerry Carter", "Putting Directories to Work."], ["Rails Recipes", "Chad Fowler", "A trusted collection of solutions."]] }
|
126
|
+
let(:valid_values_with_context) { [[1111, "Jerry Carter", "1111"], [2222, "Chad Fowler", "2222"]] }
|
127
|
+
let(:invalid_values) { [["The RSpec Book", "David Chelimsky", "..."], ["Agile+UX", "", "All about Agile in UX."]] }
|
128
|
+
let(:valid_models) { valid_values.map { |title, author_name, content| Topic.new(title: title, author_name: author_name, content: content) } }
|
129
|
+
let(:invalid_models) { invalid_values.map { |title, author_name, content| Topic.new(title: title, author_name: author_name, content: content) } }
|
130
130
|
|
131
131
|
context "with validation checks turned off" do
|
132
132
|
it "should import valid data" do
|
data/test/models/topic.rb
CHANGED
@@ -2,6 +2,8 @@ class Topic < ActiveRecord::Base
|
|
2
2
|
validates_presence_of :author_name
|
3
3
|
validates :title, numericality: { only_integer: true }, on: :context_test
|
4
4
|
validates :title, uniqueness: true
|
5
|
+
validates :content, uniqueness: true
|
6
|
+
validates :word_count, numericality: { greater_than: 0 }, if: :content?
|
5
7
|
|
6
8
|
validate -> { errors.add(:title, :validate_failed) if title == 'validate_failed' }
|
7
9
|
before_validation -> { errors.add(:title, :invalid) if title == 'invalid' }
|
@@ -10,4 +12,10 @@ class Topic < ActiveRecord::Base
|
|
10
12
|
belongs_to :parent, class_name: "Topic"
|
11
13
|
|
12
14
|
composed_of :description, mapping: [%w(title title), %w(author_name author_name)], allow_nil: true, class_name: "TopicDescription"
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def word_count
|
19
|
+
@word_count ||= content.to_s.scan(/\w+/).count
|
20
|
+
end
|
13
21
|
end
|
data/test/support/factories.rb
CHANGED
@@ -64,8 +64,8 @@ def should_support_mysql_import_functionality
|
|
64
64
|
let(:columns) { %w(id author_name title) }
|
65
65
|
|
66
66
|
setup do
|
67
|
-
topics << Topic.create!(title: "LDAP", author_name: "Big Bird")
|
68
|
-
topics << Topic.create!(title: "Rails Recipes", author_name: "Elmo")
|
67
|
+
topics << Topic.create!(title: "LDAP", author_name: "Big Bird", content: "Putting Directories to Work.")
|
68
|
+
topics << Topic.create!(title: "Rails Recipes", author_name: "Elmo", content: "A trusted collection of solutions.")
|
69
69
|
end
|
70
70
|
|
71
71
|
it "synchronizes passed in ActiveRecord model instances with the data just imported" do
|
@@ -85,6 +85,76 @@ def should_support_postgresql_import_functionality
|
|
85
85
|
assert_equal [], Book.import(books, no_returning: true).ids
|
86
86
|
end
|
87
87
|
end
|
88
|
+
|
89
|
+
describe "returning" do
|
90
|
+
let(:books) { [Book.new(author_name: "King", title: "It")] }
|
91
|
+
let(:result) { Book.import(books, returning: %w(author_name title)) }
|
92
|
+
let(:book_id) do
|
93
|
+
if RUBY_PLATFORM == 'java' || ENV['AR_VERSION'].to_i >= 5.0
|
94
|
+
books.first.id
|
95
|
+
else
|
96
|
+
books.first.id.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "creates records" do
|
101
|
+
assert_difference("Book.count", +1) { result }
|
102
|
+
end
|
103
|
+
|
104
|
+
it "returns ids" do
|
105
|
+
result
|
106
|
+
assert_equal [book_id], result.ids
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns specified columns" do
|
110
|
+
assert_equal [%w(King It)], result.results
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when primary key and returning overlap" do
|
114
|
+
let(:result) { Book.import(books, returning: %w(id title)) }
|
115
|
+
|
116
|
+
setup { result }
|
117
|
+
|
118
|
+
it "returns ids" do
|
119
|
+
assert_equal [book_id], result.ids
|
120
|
+
end
|
121
|
+
|
122
|
+
it "returns specified columns" do
|
123
|
+
assert_equal [[book_id, 'It']], result.results
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "setting model attributes" do
|
128
|
+
let(:code) { 'abc' }
|
129
|
+
let(:discount) { 0.10 }
|
130
|
+
let(:original_promotion) do
|
131
|
+
Promotion.new(code: code, discount: discount)
|
132
|
+
end
|
133
|
+
let(:updated_promotion) do
|
134
|
+
Promotion.new(code: code, description: 'ABC discount')
|
135
|
+
end
|
136
|
+
let(:returning_columns) { %w(discount) }
|
137
|
+
|
138
|
+
setup do
|
139
|
+
Promotion.import([original_promotion])
|
140
|
+
Promotion.import([updated_promotion],
|
141
|
+
on_duplicate_key_update: { conflict_target: %i(code), columns: %i(description) },
|
142
|
+
returning: returning_columns)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "sets model attributes" do
|
146
|
+
assert_equal updated_promotion.discount, discount
|
147
|
+
end
|
148
|
+
|
149
|
+
context "returning multiple columns" do
|
150
|
+
let(:returning_columns) { %w(discount description) }
|
151
|
+
|
152
|
+
it "sets model attributes" do
|
153
|
+
assert_equal updated_promotion.discount, discount
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
88
158
|
end
|
89
159
|
|
90
160
|
if ENV['AR_VERSION'].to_f >= 4.0
|
@@ -159,6 +229,17 @@ def should_support_postgresql_import_functionality
|
|
159
229
|
end
|
160
230
|
end
|
161
231
|
end
|
232
|
+
|
233
|
+
describe "with binary field" do
|
234
|
+
let(:binary_value) { "\xE0'c\xB2\xB0\xB3Bh\\\xC2M\xB1m\\I\xC4r".force_encoding('ASCII-8BIT') }
|
235
|
+
it "imports the correct values for binary fields" do
|
236
|
+
alarms = [Alarm.new(device_id: 1, alarm_type: 1, status: 1, secret_key: binary_value)]
|
237
|
+
assert_difference "Alarm.count", +1 do
|
238
|
+
Alarm.import alarms
|
239
|
+
end
|
240
|
+
assert_equal(binary_value, Alarm.first.secret_key)
|
241
|
+
end
|
242
|
+
end
|
162
243
|
end
|
163
244
|
|
164
245
|
def should_support_postgresql_upsert_functionality
|
@@ -102,6 +102,19 @@ def should_support_recursive_import
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
it "imports an imported belongs_to association id" do
|
106
|
+
books = new_topics[0].books.to_a
|
107
|
+
Topic.import new_topics, validate: false
|
108
|
+
|
109
|
+
assert_difference "Book.count", books.size do
|
110
|
+
Book.import books, validate: false
|
111
|
+
end
|
112
|
+
|
113
|
+
books.each do |book|
|
114
|
+
assert_not_nil book.topic_id
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
105
118
|
unless ENV["SKIP_COMPOSITE_PK"]
|
106
119
|
describe "with composite primary keys" do
|
107
120
|
it "should import models and set id" do
|
@@ -63,7 +63,8 @@ describe ActiveRecord::Import::ValueSetsBytesParser do
|
|
63
63
|
values = [
|
64
64
|
"('1','2','3')",
|
65
65
|
"('4','5','6')",
|
66
|
-
"('7','8','9')"
|
66
|
+
"('7','8','9')"
|
67
|
+
]
|
67
68
|
|
68
69
|
base_sql_size_in_bytes = 15
|
69
70
|
max_bytes = 30
|
@@ -88,7 +89,8 @@ describe ActiveRecord::Import::ValueSetsBytesParser do
|
|
88
89
|
# each accented e should be 2 bytes, so each entry is 6 bytes instead of 5
|
89
90
|
values = [
|
90
91
|
"('é')",
|
91
|
-
"('é')"
|
92
|
+
"('é')"
|
93
|
+
]
|
92
94
|
|
93
95
|
base_sql_size_in_bytes = 15
|
94
96
|
max_bytes = 26
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-import
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|