activerecord-import 0.25.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yaml +151 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +74 -8
  5. data/.rubocop_todo.yml +10 -16
  6. data/Brewfile +3 -1
  7. data/CHANGELOG.md +232 -2
  8. data/Dockerfile +23 -0
  9. data/Gemfile +26 -14
  10. data/LICENSE +21 -56
  11. data/README.markdown +612 -21
  12. data/Rakefile +4 -1
  13. data/activerecord-import.gemspec +6 -5
  14. data/benchmarks/benchmark.rb +10 -4
  15. data/benchmarks/lib/base.rb +4 -2
  16. data/benchmarks/lib/cli_parser.rb +4 -2
  17. data/benchmarks/lib/float.rb +2 -0
  18. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  19. data/benchmarks/lib/output_to_csv.rb +2 -0
  20. data/benchmarks/lib/output_to_html.rb +4 -2
  21. data/benchmarks/models/test_innodb.rb +2 -0
  22. data/benchmarks/models/test_memory.rb +2 -0
  23. data/benchmarks/models/test_myisam.rb +2 -0
  24. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  25. data/docker-compose.yml +34 -0
  26. data/gemfiles/4.2.gemfile +2 -0
  27. data/gemfiles/5.0.gemfile +2 -0
  28. data/gemfiles/5.1.gemfile +2 -0
  29. data/gemfiles/5.2.gemfile +2 -0
  30. data/gemfiles/6.0.gemfile +4 -0
  31. data/gemfiles/6.1.gemfile +4 -0
  32. data/gemfiles/7.0.gemfile +4 -0
  33. data/gemfiles/7.1.gemfile +3 -0
  34. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  36. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  38. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  39. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  40. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  41. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  42. data/lib/activerecord-import/active_record/adapters/trilogy_adapter.rb +8 -0
  43. data/lib/activerecord-import/adapters/abstract_adapter.rb +15 -6
  44. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  45. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  46. data/lib/activerecord-import/adapters/mysql_adapter.rb +34 -29
  47. data/lib/activerecord-import/adapters/postgresql_adapter.rb +74 -55
  48. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +138 -13
  49. data/lib/activerecord-import/adapters/trilogy_adapter.rb +7 -0
  50. data/lib/activerecord-import/base.rb +11 -2
  51. data/lib/activerecord-import/import.rb +290 -114
  52. data/lib/activerecord-import/mysql2.rb +2 -0
  53. data/lib/activerecord-import/postgresql.rb +2 -0
  54. data/lib/activerecord-import/sqlite3.rb +2 -0
  55. data/lib/activerecord-import/synchronize.rb +4 -2
  56. data/lib/activerecord-import/value_sets_parser.rb +5 -0
  57. data/lib/activerecord-import/version.rb +3 -1
  58. data/lib/activerecord-import.rb +2 -1
  59. data/test/adapters/jdbcmysql.rb +2 -0
  60. data/test/adapters/jdbcpostgresql.rb +2 -0
  61. data/test/adapters/jdbcsqlite3.rb +2 -0
  62. data/test/adapters/makara_postgis.rb +2 -0
  63. data/test/adapters/mysql2.rb +2 -0
  64. data/test/adapters/mysql2_makara.rb +2 -0
  65. data/test/adapters/mysql2spatial.rb +2 -0
  66. data/test/adapters/postgis.rb +2 -0
  67. data/test/adapters/postgresql.rb +2 -0
  68. data/test/adapters/postgresql_makara.rb +2 -0
  69. data/test/adapters/seamless_database_pool.rb +2 -0
  70. data/test/adapters/spatialite.rb +2 -0
  71. data/test/adapters/sqlite3.rb +2 -0
  72. data/test/adapters/trilogy.rb +9 -0
  73. data/test/database.yml.sample +7 -0
  74. data/test/{travis → github}/database.yml +7 -1
  75. data/test/import_test.rb +151 -8
  76. data/test/jdbcmysql/import_test.rb +5 -3
  77. data/test/jdbcpostgresql/import_test.rb +4 -2
  78. data/test/jdbcsqlite3/import_test.rb +4 -2
  79. data/test/makara_postgis/import_test.rb +4 -2
  80. data/test/models/account.rb +2 -0
  81. data/test/models/alarm.rb +2 -0
  82. data/test/models/animal.rb +8 -0
  83. data/test/models/author.rb +7 -0
  84. data/test/models/bike_maker.rb +3 -0
  85. data/test/models/book.rb +7 -2
  86. data/test/models/car.rb +2 -0
  87. data/test/models/card.rb +5 -0
  88. data/test/models/chapter.rb +2 -0
  89. data/test/models/composite_book.rb +19 -0
  90. data/test/models/composite_chapter.rb +9 -0
  91. data/test/models/customer.rb +18 -0
  92. data/test/models/deck.rb +8 -0
  93. data/test/models/dictionary.rb +2 -0
  94. data/test/models/discount.rb +2 -0
  95. data/test/models/end_note.rb +2 -0
  96. data/test/models/group.rb +2 -0
  97. data/test/models/order.rb +17 -0
  98. data/test/models/playing_card.rb +4 -0
  99. data/test/models/promotion.rb +2 -0
  100. data/test/models/question.rb +2 -0
  101. data/test/models/rule.rb +2 -0
  102. data/test/models/tag.rb +9 -1
  103. data/test/models/tag_alias.rb +11 -0
  104. data/test/models/topic.rb +7 -0
  105. data/test/models/user.rb +2 -0
  106. data/test/models/user_token.rb +3 -0
  107. data/test/models/vendor.rb +2 -0
  108. data/test/models/widget.rb +2 -0
  109. data/test/mysql2/import_test.rb +5 -3
  110. data/test/mysql2_makara/import_test.rb +5 -3
  111. data/test/mysqlspatial2/import_test.rb +5 -3
  112. data/test/postgis/import_test.rb +4 -2
  113. data/test/postgresql/import_test.rb +4 -2
  114. data/test/schema/generic_schema.rb +37 -1
  115. data/test/schema/jdbcpostgresql_schema.rb +3 -1
  116. data/test/schema/mysql2_schema.rb +2 -0
  117. data/test/schema/postgis_schema.rb +3 -1
  118. data/test/schema/postgresql_schema.rb +49 -0
  119. data/test/schema/sqlite3_schema.rb +15 -0
  120. data/test/schema/version.rb +2 -0
  121. data/test/sqlite3/import_test.rb +4 -2
  122. data/test/support/active_support/test_case_extensions.rb +2 -0
  123. data/test/support/assertions.rb +2 -0
  124. data/test/support/factories.rb +10 -8
  125. data/test/support/generate.rb +10 -8
  126. data/test/support/mysql/import_examples.rb +2 -1
  127. data/test/support/postgresql/import_examples.rb +152 -3
  128. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  129. data/test/support/shared_examples/on_duplicate_key_update.rb +122 -9
  130. data/test/support/shared_examples/recursive_import.rb +128 -2
  131. data/test/support/sqlite3/import_examples.rb +191 -26
  132. data/test/synchronize_test.rb +2 -0
  133. data/test/test_helper.rb +34 -7
  134. data/test/trilogy/import_test.rb +7 -0
  135. data/test/value_sets_bytes_parser_test.rb +3 -1
  136. data/test/value_sets_records_parser_test.rb +3 -1
  137. metadata +46 -16
  138. data/.travis.yml +0 -71
  139. data/gemfiles/3.2.gemfile +0 -2
  140. data/gemfiles/4.0.gemfile +0 -2
  141. data/gemfiles/4.1.gemfile +0 -2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  warn <<-MSG
2
4
  [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
5
  is deprecated. Update to autorequire using 'require "activerecord-import"'. See
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  warn <<-MSG
2
4
  [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
5
  is deprecated. Update to autorequire using 'require "activerecord-import"'. See
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  warn <<-MSG
2
4
  [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
5
  is deprecated. Update to autorequire using 'require "activerecord-import"'. See
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord # :nodoc:
2
4
  class Base # :nodoc:
3
5
  # Synchronizes the passed in ActiveRecord instances with data
@@ -39,8 +41,8 @@ module ActiveRecord # :nodoc:
39
41
 
40
42
  next unless matched_instance
41
43
 
42
- instance.send :clear_aggregation_cache
43
- instance.send :clear_association_cache
44
+ instance.instance_variable_set :@association_cache, {}
45
+ instance.send :clear_aggregation_cache if instance.respond_to?(:clear_aggregation_cache, true)
44
46
  instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
45
47
 
46
48
  if instance.respond_to?(:clear_changes_information)
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array'
4
+
1
5
  module ActiveRecord::Import
2
6
  class ValueSetTooLargeError < StandardError
3
7
  attr_reader :size
8
+
4
9
  def initialize(msg = "Value set exceeds max size", size = 0)
5
10
  @size = size
6
11
  super(msg)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Import
3
- VERSION = "0.25.0".freeze
5
+ VERSION = "1.7.0"
4
6
  end
5
7
  end
@@ -1,4 +1,5 @@
1
- # rubocop:disable Style/FileName
1
+ # frozen_string_literal: true
2
+
2
3
  require "active_support/lazy_load_hooks"
3
4
 
4
5
  ActiveSupport.on_load(:active_record) do
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "jdbcmysql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "jdbcpostgresql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "jdbcsqlite3"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgis"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "mysql2"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "mysql2_makara"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "mysql2spatial"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgis"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgresql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgresql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "seamless_database_pool"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "spatialite"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "sqlite3"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["ARE_DB"] = "trilogy"
4
+
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ require "activerecord-trilogy-adapter"
7
+ require "trilogy_adapter/connection"
8
+ ActiveRecord::Base.extend TrilogyAdapter::Connection
9
+ end
@@ -8,6 +8,7 @@ common: &common
8
8
  mysql2: &mysql2
9
9
  <<: *common
10
10
  adapter: mysql2
11
+ host: mysql
11
12
 
12
13
  mysql2spatial:
13
14
  <<: *mysql2
@@ -19,6 +20,7 @@ postgresql: &postgresql
19
20
  <<: *common
20
21
  username: postgres
21
22
  adapter: postgresql
23
+ host: postgresql
22
24
  min_messages: warning
23
25
 
24
26
  postresql_makara:
@@ -50,3 +52,8 @@ sqlite3: &sqlite3
50
52
 
51
53
  spatialite:
52
54
  <<: *sqlite3
55
+
56
+ trilogy:
57
+ <<: *common
58
+ adapter: trilogy
59
+ host: mysql
@@ -1,7 +1,8 @@
1
1
  common: &common
2
2
  username: root
3
- password:
3
+ password: root
4
4
  encoding: utf8
5
+ collation: utf8_general_ci
5
6
  host: localhost
6
7
  database: activerecord_import_test
7
8
 
@@ -37,6 +38,7 @@ oracle:
37
38
  postgresql: &postgresql
38
39
  <<: *common
39
40
  username: postgres
41
+ password: postgres
40
42
  adapter: postgresql
41
43
  min_messages: warning
42
44
 
@@ -64,3 +66,7 @@ sqlite3: &sqlite3
64
66
 
65
67
  spatialite:
66
68
  <<: *sqlite3
69
+
70
+ trilogy:
71
+ <<: *common
72
+ adapter: trilogy
data/test/import_test.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path('../test_helper', __FILE__)
2
4
 
3
5
  describe "#import" do
@@ -17,6 +19,11 @@ describe "#import" do
17
19
  assert_equal error.message, "Last argument should be a two dimensional array '[[]]'. First element in array was a String"
18
20
  end
19
21
 
22
+ it "warns you that you're passing more data than you ought to" do
23
+ error = assert_raise(ArgumentError) { Topic.import %w(title author_name), [['Author #1', 'Book #1', 0]] }
24
+ assert_equal error.message, "Number of values (8) exceeds number of columns (7)"
25
+ end
26
+
20
27
  it "should not produce an error when importing empty arrays" do
21
28
  assert_nothing_raised do
22
29
  Topic.import []
@@ -154,6 +161,25 @@ describe "#import" do
154
161
  Tag.import columns, values, validate: false
155
162
  end
156
163
  end
164
+
165
+ it "should import models that are required to belong to models with composite primary keys" do
166
+ tag = Tag.create!(tag_id: 1, publisher_id: 1, tag: 'Mystery')
167
+ valid_tag_alias = TagAlias.new(tag_id: tag.tag_id, parent_id: tag.publisher_id, alias: 'Detective')
168
+ invalid_tag_aliases = [
169
+ TagAlias.new(tag_id: nil, parent_id: nil, alias: 'Detective'),
170
+ TagAlias.new(tag_id: tag.tag_id, parent_id: nil, alias: 'Detective'),
171
+ TagAlias.new(tag_id: nil, parent_id: tag.publisher_id, alias: 'Detective'),
172
+ ]
173
+
174
+ assert_difference "TagAlias.count", +1 do
175
+ TagAlias.import [valid_tag_alias]
176
+ end
177
+ invalid_tag_aliases.each do |invalid_tag_alias|
178
+ assert_no_difference "TagAlias.count" do
179
+ TagAlias.import [invalid_tag_alias]
180
+ end
181
+ end
182
+ end
157
183
  end
158
184
  end
159
185
 
@@ -164,7 +190,17 @@ describe "#import" do
164
190
  assert_difference "Dictionary.count", +1 do
165
191
  Dictionary.import dictionaries
166
192
  end
167
- assert_equal "Dictionary", Dictionary.first.type
193
+ assert_equal "Dictionary", Dictionary.last.type
194
+ end
195
+
196
+ it "should import arrays successfully" do
197
+ columns = [:author_name, :title]
198
+ values = [["Noah Webster", "Webster's Dictionary"]]
199
+
200
+ assert_difference "Dictionary.count", +1 do
201
+ Dictionary.import columns, values
202
+ end
203
+ assert_equal "Dictionary", Dictionary.last.type
168
204
  end
169
205
  end
170
206
 
@@ -210,9 +246,9 @@ describe "#import" do
210
246
  end
211
247
 
212
248
  it "should ignore uniqueness validators" do
213
- Topic.import columns, valid_values, validate: true
249
+ Topic.import columns, valid_values
214
250
  assert_difference "Topic.count", +2 do
215
- Topic.import columns, valid_values, validate: true
251
+ Topic.import columns, valid_values
216
252
  end
217
253
  end
218
254
 
@@ -247,6 +283,16 @@ describe "#import" do
247
283
  end
248
284
  end
249
285
 
286
+ it "should index the failed instances by their poistion in the set if `track_failures` is true" do
287
+ index_offset = valid_values.length
288
+ results = Topic.import columns, valid_values + invalid_values, validate: true, track_validation_failures: true
289
+ assert_equal invalid_values.size, results.failed_instances.size
290
+ invalid_values.each_with_index do |value_set, index|
291
+ assert_equal index + index_offset, results.failed_instances[index].first
292
+ assert_equal value_set.first, results.failed_instances[index].last.title
293
+ end
294
+ end
295
+
250
296
  it "should set ids in valid models if adapter supports setting primary key of imported objects" do
251
297
  if ActiveRecord::Base.supports_setting_primary_key_of_imported_objects?
252
298
  Topic.import (invalid_models + valid_models), validate: true
@@ -294,6 +340,36 @@ describe "#import" do
294
340
  end
295
341
  end
296
342
  end
343
+
344
+ context "with uniqueness validators included" do
345
+ it "should not import duplicate records" do
346
+ Topic.import columns, valid_values
347
+ assert_no_difference "Topic.count" do
348
+ Topic.import columns, valid_values, validate_uniqueness: true
349
+ end
350
+ end
351
+ end
352
+
353
+ context "when validatoring presence of belongs_to association" do
354
+ it "should not import records without foreign key" do
355
+ assert_no_difference "UserToken.count" do
356
+ UserToken.import [:token], [['12345abcdef67890']]
357
+ end
358
+ end
359
+
360
+ it "should import records with foreign key" do
361
+ assert_difference "UserToken.count", +1 do
362
+ UserToken.import [:user_name, :token], [%w("Bob", "12345abcdef67890")]
363
+ end
364
+ end
365
+
366
+ it "should not mutate the defined validations" do
367
+ UserToken.import [:user_name, :token], [%w("Bob", "12345abcdef67890")]
368
+ ut = UserToken.new
369
+ ut.valid?
370
+ assert_includes ut.errors.messages, :user
371
+ end
372
+ end
297
373
  end
298
374
 
299
375
  context "without :validation option" do
@@ -360,6 +436,15 @@ describe "#import" do
360
436
  assert_equal 3, result.num_inserts if Topic.supports_import?
361
437
  end
362
438
  end
439
+
440
+ it "should accept and call an optional callable to run after each batch" do
441
+ lambda_called = 0
442
+
443
+ my_proc = ->(_row_count, _batches, _batch, _duration) { lambda_called += 1 }
444
+ Topic.import Build(10, :topics), batch_size: 4, batch_progress: my_proc
445
+
446
+ assert_equal 3, lambda_called
447
+ end
363
448
  end
364
449
 
365
450
  context "with :synchronize option" do
@@ -490,11 +575,15 @@ describe "#import" do
490
575
 
491
576
  context "when the timestamps columns are present" do
492
577
  setup do
493
- @existing_book = Book.create(title: "Fell", author_name: "Curry", publisher: "Bayer", created_at: 2.years.ago.utc, created_on: 2.years.ago.utc)
494
- ActiveRecord::Base.default_timezone = :utc
578
+ @existing_book = Book.create(title: "Fell", author_name: "Curry", publisher: "Bayer", created_at: 2.years.ago.utc, created_on: 2.years.ago.utc, updated_at: 2.years.ago.utc, updated_on: 2.years.ago.utc)
579
+ if ActiveRecord.respond_to?(:default_timezone)
580
+ ActiveRecord.default_timezone = :utc
581
+ else
582
+ ActiveRecord::Base.default_timezone = :utc
583
+ end
495
584
  Timecop.freeze(time) do
496
585
  assert_difference "Book.count", +2 do
497
- Book.import %w(title author_name publisher created_at created_on), [["LDAP", "Big Bird", "Del Rey", nil, nil], [@existing_book.title, @existing_book.author_name, @existing_book.publisher, @existing_book.created_at, @existing_book.created_on]]
586
+ Book.import %w(title author_name publisher created_at created_on updated_at updated_on), [["LDAP", "Big Bird", "Del Rey", nil, nil, nil, nil], [@existing_book.title, @existing_book.author_name, @existing_book.publisher, @existing_book.created_at, @existing_book.created_on, @existing_book.updated_at, @existing_book.updated_on]]
498
587
  end
499
588
  end
500
589
  @new_book, @existing_book = Book.last 2
@@ -523,6 +612,23 @@ describe "#import" do
523
612
  it "should set the updated_on column for new records" do
524
613
  assert_in_delta time.to_i, @new_book.updated_on.to_i, 1.second
525
614
  end
615
+
616
+ it "should not set the updated_at column for existing records" do
617
+ assert_equal 2.years.ago.utc.strftime("%Y:%d"), @existing_book.updated_at.strftime("%Y:%d")
618
+ end
619
+
620
+ it "should not set the updated_on column for existing records" do
621
+ assert_equal 2.years.ago.utc.strftime("%Y:%d"), @existing_book.updated_on.strftime("%Y:%d")
622
+ end
623
+
624
+ it "should not set the updated_at column on models if changed" do
625
+ timestamp = Time.now.utc
626
+ books = [
627
+ Book.new(author_name: "Foo", title: "Baz", created_at: timestamp, updated_at: timestamp)
628
+ ]
629
+ Book.import books
630
+ assert_equal timestamp.strftime("%Y:%d"), Book.last.updated_at.strftime("%Y:%d")
631
+ end
526
632
  end
527
633
 
528
634
  context "when a custom time zone is set" do
@@ -567,7 +673,7 @@ describe "#import" do
567
673
 
568
674
  context "importing through an association scope" do
569
675
  { has_many: :chapters, polymorphic: :discounts }.each do |association_type, association|
570
- book = FactoryGirl.create :book
676
+ book = FactoryBot.create :book
571
677
  scope = book.public_send association
572
678
  klass = { chapters: Chapter, discounts: Discount }[association]
573
679
  column = { chapters: :title, discounts: :amount }[association]
@@ -590,6 +696,14 @@ describe "#import" do
590
696
  assert_equal [val1, val2], scope.map(&column).sort
591
697
  end
592
698
 
699
+ context "for cards and decks" do
700
+ it "works when the polymorphic name is different than base class name" do
701
+ deck = Deck.create(id: 1, name: 'test')
702
+ deck.cards.import [:id, :deck_type], [[1, 'PlayingCard']]
703
+ assert_equal deck.cards.first.deck_type, "PlayingCard"
704
+ end
705
+ end
706
+
593
707
  it "works importing array of hashes" do
594
708
  scope.import [{ column => val1 }, { column => val2 }]
595
709
 
@@ -609,7 +723,7 @@ describe "#import" do
609
723
 
610
724
  context "importing model with polymorphic belongs_to" do
611
725
  it "works without error" do
612
- book = FactoryGirl.create :book
726
+ book = FactoryBot.create :book
613
727
  discount = Discount.new(discountable: book)
614
728
 
615
729
  Discount.import([discount])
@@ -848,4 +962,33 @@ describe "#import" do
848
962
  end
849
963
  end
850
964
  end
965
+ describe "importing model with after_initialize callback" do
966
+ let(:columns) { %w(name size) }
967
+ let(:valid_values) { [%w("Deer", "Small"), %w("Monkey", "Medium")] }
968
+ let(:invalid_values) do
969
+ [
970
+ { name: "giraffe", size: "Large" },
971
+ { size: "Medium" } # name is missing
972
+ ]
973
+ end
974
+ context "with validation checks turned off" do
975
+ it "should import valid data" do
976
+ Animal.import(columns, valid_values, validate: false)
977
+ assert_equal 2, Animal.count
978
+ end
979
+ it "should raise ArgumentError" do
980
+ assert_raise(ArgumentError) { Animal.import(invalid_values, validate: false) }
981
+ end
982
+ end
983
+
984
+ context "with validation checks turned on" do
985
+ it "should import valid data" do
986
+ Animal.import(columns, valid_values, validate: true)
987
+ assert_equal 2, Animal.count
988
+ end
989
+ it "should raise ArgumentError" do
990
+ assert_raise(ArgumentError) { Animal.import(invalid_values, validate: true) }
991
+ end
992
+ end
993
+ end
851
994
  end
@@ -1,5 +1,7 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
3
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
4
6
 
5
7
  should_support_mysql_import_functionality
@@ -1,4 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
3
5
 
4
6
  should_support_postgresql_import_functionality
@@ -1,4 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/sqlite3/import_examples")
3
5
 
4
6
  should_support_sqlite3_import_functionality
@@ -1,5 +1,7 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
3
5
 
4
6
  should_support_postgresql_import_functionality
5
7
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Account < ActiveRecord::Base
2
4
  self.locking_column = :lock
3
5
  end
data/test/models/alarm.rb CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Alarm < ActiveRecord::Base
2
4
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Animal < ActiveRecord::Base
4
+ after_initialize :validate_name_presence, if: :new_record?
5
+ def validate_name_presence
6
+ raise ArgumentError if name.nil?
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Author < ActiveRecord::Base
4
+ if ENV['AR_VERSION'].to_f >= 7.1
5
+ has_many :composite_books, query_constraints: [:id, :author_id], inverse_of: :author
6
+ end
7
+ end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bike
2
4
  def self.table_name_prefix
3
5
  'bike_'
4
6
  end
7
+
5
8
  class Maker < ActiveRecord::Base
6
9
  end
7
10
  end
data/test/models/book.rb CHANGED
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Book < ActiveRecord::Base
2
4
  belongs_to :topic, inverse_of: :books
3
- belongs_to :tag, foreign_key: [:tag_id, :parent_id]
4
-
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ belongs_to :tag, foreign_key: [:tag_id, :parent_id] unless ENV["SKIP_COMPOSITE_PK"]
7
+ else
8
+ belongs_to :tag, query_constraints: [:tag_id, :parent_id] unless ENV["SKIP_COMPOSITE_PK"]
9
+ end
5
10
  has_many :chapters, inverse_of: :book
6
11
  has_many :discounts, as: :discountable
7
12
  has_many :end_notes, inverse_of: :book
data/test/models/car.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Car < ActiveRecord::Base
2
4
  self.primary_key = :Name
3
5
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Card < ActiveRecord::Base
4
+ belongs_to :deck, polymorphic: true
5
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Chapter < ActiveRecord::Base
2
4
  belongs_to :book, inverse_of: :chapters
3
5
  validates :title, presence: true
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CompositeBook < ActiveRecord::Base
4
+ self.primary_key = %i[id author_id]
5
+ belongs_to :author
6
+ if ENV['AR_VERSION'].to_f <= 7.0
7
+ unless ENV["SKIP_COMPOSITE_PK"]
8
+ has_many :composite_chapters, inverse_of: :composite_book,
9
+ foreign_key: [:id, :author_id]
10
+ end
11
+ else
12
+ has_many :composite_chapters, inverse_of: :composite_book,
13
+ query_constraints: [:id, :author_id]
14
+ end
15
+
16
+ def self.sequence_name
17
+ "composite_book_id_seq"
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CompositeChapter < ActiveRecord::Base
4
+ if ENV['AR_VERSION'].to_f >= 7.1
5
+ belongs_to :composite_book, inverse_of: :composite_chapters,
6
+ query_constraints: [:composite_book_id, :author_id]
7
+ end
8
+ validates :title, presence: true
9
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Customer < ActiveRecord::Base
4
+ unless ENV["SKIP_COMPOSITE_PK"]
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ has_many :orders,
7
+ inverse_of: :customer,
8
+ primary_key: %i(account_id id),
9
+ foreign_key: %i(account_id customer_id)
10
+ else
11
+ has_many :orders,
12
+ inverse_of: :customer,
13
+ primary_key: %i(account_id id),
14
+ query_constraints: %i(account_id customer_id)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Deck < ActiveRecord::Base
4
+ has_many :cards
5
+ def self.polymorphic_name
6
+ "PlayingCard"
7
+ end
8
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'book'
2
4
 
3
5
  class Dictionary < Book
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Discount < ActiveRecord::Base
2
4
  belongs_to :discountable, polymorphic: true
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class EndNote < ActiveRecord::Base
2
4
  belongs_to :book, inverse_of: :end_notes
3
5
  validates :note, presence: true