activerecord-import 0.10.0 → 1.0.8
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 +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +49 -0
- data/.rubocop_todo.yml +36 -0
- data/.travis.yml +64 -8
- data/CHANGELOG.md +475 -0
- data/Gemfile +32 -15
- data/LICENSE +21 -56
- data/README.markdown +564 -35
- data/Rakefile +20 -3
- data/activerecord-import.gemspec +7 -7
- data/benchmarks/README +2 -2
- data/benchmarks/benchmark.rb +68 -64
- data/benchmarks/lib/base.rb +138 -137
- data/benchmarks/lib/cli_parser.rb +107 -103
- data/benchmarks/lib/{mysql_benchmark.rb → mysql2_benchmark.rb} +19 -22
- data/benchmarks/lib/output_to_csv.rb +5 -4
- data/benchmarks/lib/output_to_html.rb +8 -13
- data/benchmarks/models/test_innodb.rb +1 -1
- data/benchmarks/models/test_memory.rb +1 -1
- data/benchmarks/models/test_myisam.rb +1 -1
- data/benchmarks/schema/mysql2_schema.rb +16 -0
- data/gemfiles/3.2.gemfile +2 -4
- data/gemfiles/4.0.gemfile +2 -4
- data/gemfiles/4.1.gemfile +2 -4
- data/gemfiles/4.2.gemfile +2 -4
- data/gemfiles/5.0.gemfile +2 -0
- data/gemfiles/5.1.gemfile +2 -0
- data/gemfiles/5.2.gemfile +2 -0
- data/gemfiles/6.0.gemfile +2 -0
- data/gemfiles/6.1.gemfile +1 -0
- data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +0 -1
- data/lib/activerecord-import/adapters/abstract_adapter.rb +23 -17
- data/lib/activerecord-import/adapters/mysql_adapter.rb +52 -25
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +187 -10
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +148 -17
- data/lib/activerecord-import/base.rb +15 -9
- data/lib/activerecord-import/import.rb +740 -191
- data/lib/activerecord-import/synchronize.rb +21 -21
- data/lib/activerecord-import/value_sets_parser.rb +33 -8
- data/lib/activerecord-import/version.rb +1 -1
- data/lib/activerecord-import.rb +4 -15
- data/test/adapters/jdbcsqlite3.rb +1 -0
- data/test/adapters/makara_postgis.rb +1 -0
- data/test/adapters/mysql2_makara.rb +1 -0
- data/test/adapters/mysql2spatial.rb +1 -1
- data/test/adapters/postgis.rb +1 -1
- data/test/adapters/postgresql.rb +1 -1
- data/test/adapters/postgresql_makara.rb +1 -0
- data/test/adapters/spatialite.rb +1 -1
- data/test/adapters/sqlite3.rb +1 -1
- data/test/database.yml.sample +13 -18
- data/test/import_test.rb +608 -89
- data/test/jdbcmysql/import_test.rb +2 -3
- data/test/jdbcpostgresql/import_test.rb +0 -2
- data/test/jdbcsqlite3/import_test.rb +4 -0
- data/test/makara_postgis/import_test.rb +8 -0
- data/test/models/account.rb +3 -0
- data/test/models/alarm.rb +2 -0
- data/test/models/animal.rb +6 -0
- data/test/models/bike_maker.rb +7 -0
- data/test/models/book.rb +7 -6
- data/test/models/car.rb +3 -0
- data/test/models/chapter.rb +2 -2
- data/test/models/dictionary.rb +4 -0
- data/test/models/discount.rb +3 -0
- data/test/models/end_note.rb +2 -2
- data/test/models/promotion.rb +3 -0
- data/test/models/question.rb +3 -0
- data/test/models/rule.rb +3 -0
- data/test/models/tag.rb +4 -0
- data/test/models/topic.rb +17 -3
- data/test/models/user.rb +3 -0
- data/test/models/user_token.rb +4 -0
- data/test/models/vendor.rb +7 -0
- data/test/models/widget.rb +19 -2
- data/test/mysql2/import_test.rb +2 -3
- data/test/{em_mysql2 → mysql2_makara}/import_test.rb +1 -1
- data/test/mysqlspatial2/import_test.rb +2 -2
- data/test/postgis/import_test.rb +5 -1
- data/test/schema/generic_schema.rb +159 -85
- data/test/schema/jdbcpostgresql_schema.rb +1 -0
- data/test/schema/mysql2_schema.rb +19 -0
- data/test/schema/postgis_schema.rb +1 -0
- data/test/schema/postgresql_schema.rb +61 -0
- data/test/schema/sqlite3_schema.rb +13 -0
- data/test/sqlite3/import_test.rb +2 -50
- data/test/support/active_support/test_case_extensions.rb +21 -13
- data/test/support/{mysql/assertions.rb → assertions.rb} +20 -2
- data/test/support/factories.rb +39 -14
- data/test/support/generate.rb +10 -10
- data/test/support/mysql/import_examples.rb +49 -98
- data/test/support/postgresql/import_examples.rb +535 -57
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +43 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +378 -0
- data/test/support/shared_examples/recursive_import.rb +225 -0
- data/test/support/sqlite3/import_examples.rb +231 -0
- data/test/synchronize_test.rb +10 -2
- data/test/test_helper.rb +36 -8
- data/test/travis/database.yml +26 -17
- data/test/value_sets_bytes_parser_test.rb +25 -17
- data/test/value_sets_records_parser_test.rb +6 -6
- metadata +86 -42
- data/benchmarks/boot.rb +0 -18
- data/benchmarks/schema/mysql_schema.rb +0 -16
- data/gemfiles/3.1.gemfile +0 -4
- data/lib/activerecord-import/active_record/adapters/em_mysql2_adapter.rb +0 -8
- data/lib/activerecord-import/active_record/adapters/mysql_adapter.rb +0 -6
- data/lib/activerecord-import/em_mysql2.rb +0 -7
- data/lib/activerecord-import/mysql.rb +0 -7
- data/test/adapters/em_mysql2.rb +0 -1
- data/test/adapters/mysql.rb +0 -1
- data/test/adapters/mysqlspatial.rb +0 -1
- data/test/mysql/import_test.rb +0 -6
- data/test/mysqlspatial/import_test.rb +0 -6
- data/test/schema/mysql_schema.rb +0 -18
- data/test/travis/build.sh +0 -30
data/test/import_test.rb
CHANGED
|
@@ -12,6 +12,16 @@ describe "#import" do
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
it "warns you that you're using the library wrong" do
|
|
16
|
+
error = assert_raise(ArgumentError) { Topic.import %w(title author_name), ['Author #1', 'Book #1', 0] }
|
|
17
|
+
assert_equal error.message, "Last argument should be a two dimensional array '[[]]'. First element in array was a String"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "warns you that you're passing more data than you ought to" do
|
|
21
|
+
error = assert_raise(ArgumentError) { Topic.import %w(title author_name), [['Author #1', 'Book #1', 0]] }
|
|
22
|
+
assert_equal error.message, "Number of values (8) exceeds number of columns (7)"
|
|
23
|
+
end
|
|
24
|
+
|
|
15
25
|
it "should not produce an error when importing empty arrays" do
|
|
16
26
|
assert_nothing_raised do
|
|
17
27
|
Topic.import []
|
|
@@ -19,6 +29,23 @@ describe "#import" do
|
|
|
19
29
|
end
|
|
20
30
|
end
|
|
21
31
|
|
|
32
|
+
describe "argument safety" do
|
|
33
|
+
it "should not modify the passed in columns array" do
|
|
34
|
+
assert_nothing_raised do
|
|
35
|
+
columns = %w(title author_name).freeze
|
|
36
|
+
Topic.import columns, [%w(foo bar)]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should not modify the passed in values array" do
|
|
41
|
+
assert_nothing_raised do
|
|
42
|
+
record = %w(foo bar).freeze
|
|
43
|
+
values = [record].freeze
|
|
44
|
+
Topic.import %w(title author_name), values
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
22
49
|
describe "with non-default ActiveRecord models" do
|
|
23
50
|
context "that have a non-standard primary key (that is no sequence)" do
|
|
24
51
|
it "should import models successfully" do
|
|
@@ -26,31 +53,150 @@ describe "#import" do
|
|
|
26
53
|
Widget.import Build(3, :widgets)
|
|
27
54
|
end
|
|
28
55
|
end
|
|
56
|
+
|
|
57
|
+
context "with uppercase letters" do
|
|
58
|
+
it "should import models successfully" do
|
|
59
|
+
assert_difference "Car.count", +3 do
|
|
60
|
+
Car.import Build(3, :cars)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "that have no primary key" do
|
|
67
|
+
it "should import models successfully" do
|
|
68
|
+
assert_difference "Rule.count", +3 do
|
|
69
|
+
Rule.import Build(3, :rules)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "with an array of hashes" do
|
|
76
|
+
let(:columns) { [:title, :author_name] }
|
|
77
|
+
let(:values) { [{ title: "LDAP", author_name: "Jerry Carter", author_email_address: "jcarter@test.com" }, { title: "Rails Recipes", author_name: "Chad Fowler", author_email_address: "cfowler@test.com" }] }
|
|
78
|
+
|
|
79
|
+
it "should import hash data successfully" do
|
|
80
|
+
assert_difference "Topic.count", +2 do
|
|
81
|
+
Topic.import values, validate: false
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "should import specified hash data successfully" do
|
|
86
|
+
assert_difference "Topic.count", +2 do
|
|
87
|
+
Topic.import columns, values, validate: false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Topic.all.each do |t|
|
|
91
|
+
assert_nil t.author_email_address
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "with extra keys" do
|
|
96
|
+
let(:values) do
|
|
97
|
+
[
|
|
98
|
+
{ title: "LDAP", author_name: "Jerry Carter" },
|
|
99
|
+
{ title: "Rails Recipes", author_name: "Chad Fowler", author_email_address: "cfowler@test.com" } # author_email_address is unknown
|
|
100
|
+
]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "should fail when column names are not specified" do
|
|
104
|
+
err = assert_raises ArgumentError do
|
|
105
|
+
Topic.import values, validate: false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
assert err.message.include? 'Extra keys: [:author_email_address]'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should succeed when column names are specified" do
|
|
112
|
+
assert_difference "Topic.count", +2 do
|
|
113
|
+
Topic.import columns, values, validate: false
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context "with missing keys" do
|
|
119
|
+
let(:values) do
|
|
120
|
+
[
|
|
121
|
+
{ title: "LDAP", author_name: "Jerry Carter" },
|
|
122
|
+
{ title: "Rails Recipes" } # author_name is missing
|
|
123
|
+
]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "should fail when column names are not specified" do
|
|
127
|
+
err = assert_raises ArgumentError do
|
|
128
|
+
Topic.import values, validate: false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
assert err.message.include? 'Missing keys: [:author_name]'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "should fail on missing hash key from specified column names" do
|
|
135
|
+
err = assert_raises ArgumentError do
|
|
136
|
+
Topic.import %i(author_name), values, validate: false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
assert err.message.include? 'Missing keys: [:author_name]'
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
unless ENV["SKIP_COMPOSITE_PK"]
|
|
145
|
+
describe "with composite primary keys" do
|
|
146
|
+
it "should import models successfully" do
|
|
147
|
+
tags = [Tag.new(tag_id: 1, publisher_id: 1, tag: 'Mystery')]
|
|
148
|
+
|
|
149
|
+
assert_difference "Tag.count", +1 do
|
|
150
|
+
Tag.import tags
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "should import array of values successfully" do
|
|
155
|
+
columns = [:tag_id, :publisher_id, :tag]
|
|
156
|
+
values = [[1, 1, 'Mystery'], [2, 1, 'Science']]
|
|
157
|
+
|
|
158
|
+
assert_difference "Tag.count", +2 do
|
|
159
|
+
Tag.import columns, values, validate: false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe "with STI models" do
|
|
166
|
+
it "should import models successfully" do
|
|
167
|
+
dictionaries = [Dictionary.new(author_name: "Noah Webster", title: "Webster's Dictionary")]
|
|
168
|
+
|
|
169
|
+
assert_difference "Dictionary.count", +1 do
|
|
170
|
+
Dictionary.import dictionaries
|
|
171
|
+
end
|
|
172
|
+
assert_equal "Dictionary", Dictionary.first.type
|
|
29
173
|
end
|
|
30
174
|
end
|
|
31
175
|
|
|
32
176
|
context "with :validation option" do
|
|
33
|
-
let(:columns) { %w(title author_name) }
|
|
34
|
-
let(:valid_values) { [[
|
|
35
|
-
let(:valid_values_with_context) { [[
|
|
36
|
-
let(:invalid_values) { [[
|
|
177
|
+
let(:columns) { %w(title author_name content) }
|
|
178
|
+
let(:valid_values) { [["LDAP", "Jerry Carter", "Putting Directories to Work."], ["Rails Recipes", "Chad Fowler", "A trusted collection of solutions."]] }
|
|
179
|
+
let(:valid_values_with_context) { [[1111, "Jerry Carter", "1111"], [2222, "Chad Fowler", "2222"]] }
|
|
180
|
+
let(:invalid_values) { [["The RSpec Book", "David Chelimsky", "..."], ["Agile+UX", "", "All about Agile in UX."]] }
|
|
181
|
+
let(:valid_models) { valid_values.map { |title, author_name, content| Topic.new(title: title, author_name: author_name, content: content) } }
|
|
182
|
+
let(:invalid_models) { invalid_values.map { |title, author_name, content| Topic.new(title: title, author_name: author_name, content: content) } }
|
|
37
183
|
|
|
38
184
|
context "with validation checks turned off" do
|
|
39
185
|
it "should import valid data" do
|
|
40
186
|
assert_difference "Topic.count", +2 do
|
|
41
|
-
|
|
187
|
+
Topic.import columns, valid_values, validate: false
|
|
42
188
|
end
|
|
43
189
|
end
|
|
44
190
|
|
|
45
191
|
it "should import invalid data" do
|
|
46
192
|
assert_difference "Topic.count", +2 do
|
|
47
|
-
|
|
193
|
+
Topic.import columns, invalid_values, validate: false
|
|
48
194
|
end
|
|
49
195
|
end
|
|
50
196
|
|
|
51
197
|
it 'should raise a specific error if a column does not exist' do
|
|
52
198
|
assert_raises ActiveRecord::Import::MissingColumnError do
|
|
53
|
-
Topic.import ['foo'], [['bar']], :
|
|
199
|
+
Topic.import ['foo'], [['bar']], validate: false
|
|
54
200
|
end
|
|
55
201
|
end
|
|
56
202
|
end
|
|
@@ -58,88 +204,220 @@ describe "#import" do
|
|
|
58
204
|
context "with validation checks turned on" do
|
|
59
205
|
it "should import valid data" do
|
|
60
206
|
assert_difference "Topic.count", +2 do
|
|
61
|
-
|
|
207
|
+
Topic.import columns, valid_values, validate: true
|
|
62
208
|
end
|
|
63
209
|
end
|
|
64
210
|
|
|
65
211
|
it "should import valid data with on option" do
|
|
66
212
|
assert_difference "Topic.count", +2 do
|
|
67
|
-
|
|
213
|
+
Topic.import columns, valid_values_with_context, validate_with_context: :context_test
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "should ignore uniqueness validators" do
|
|
218
|
+
Topic.import columns, valid_values
|
|
219
|
+
assert_difference "Topic.count", +2 do
|
|
220
|
+
Topic.import columns, valid_values
|
|
68
221
|
end
|
|
69
222
|
end
|
|
70
223
|
|
|
224
|
+
it "should not alter the callback chain of the model" do
|
|
225
|
+
attributes = columns.zip(valid_values.first).to_h
|
|
226
|
+
topic = Topic.new attributes
|
|
227
|
+
Topic.import [topic], validate: true
|
|
228
|
+
duplicate_topic = Topic.new attributes
|
|
229
|
+
Topic.import [duplicate_topic], validate: true
|
|
230
|
+
assert duplicate_topic.invalid?
|
|
231
|
+
end
|
|
232
|
+
|
|
71
233
|
it "should not import invalid data" do
|
|
72
234
|
assert_no_difference "Topic.count" do
|
|
73
|
-
|
|
235
|
+
Topic.import columns, invalid_values, validate: true
|
|
74
236
|
end
|
|
75
237
|
end
|
|
76
238
|
|
|
77
239
|
it "should import invalid data with on option" do
|
|
78
240
|
assert_no_difference "Topic.count" do
|
|
79
|
-
|
|
241
|
+
Topic.import columns, valid_values, validate_with_context: :context_test
|
|
80
242
|
end
|
|
81
243
|
end
|
|
82
244
|
|
|
83
245
|
it "should report the failed instances" do
|
|
84
|
-
results = Topic.import columns, invalid_values, :
|
|
246
|
+
results = Topic.import columns, invalid_values, validate: true
|
|
247
|
+
assert_equal invalid_values.size, results.failed_instances.size
|
|
248
|
+
assert_not_equal results.failed_instances.first, results.failed_instances.last
|
|
249
|
+
results.failed_instances.each do |e|
|
|
250
|
+
assert_kind_of Topic, e
|
|
251
|
+
assert_equal e.errors.count, 1
|
|
252
|
+
end
|
|
253
|
+
end
|
|
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
|
|
85
258
|
assert_equal invalid_values.size, results.failed_instances.size
|
|
86
|
-
|
|
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
|
+
|
|
265
|
+
it "should set ids in valid models if adapter supports setting primary key of imported objects" do
|
|
266
|
+
if ActiveRecord::Base.supports_setting_primary_key_of_imported_objects?
|
|
267
|
+
Topic.import (invalid_models + valid_models), validate: true
|
|
268
|
+
assert_nil invalid_models[0].id
|
|
269
|
+
assert_nil invalid_models[1].id
|
|
270
|
+
assert_equal valid_models[0].id, Topic.all[0].id
|
|
271
|
+
assert_equal valid_models[1].id, Topic.all[1].id
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it "should set ActiveRecord timestamps in valid models if adapter supports setting primary key of imported objects" do
|
|
276
|
+
if ActiveRecord::Base.supports_setting_primary_key_of_imported_objects?
|
|
277
|
+
Timecop.freeze(Time.at(0)) do
|
|
278
|
+
Topic.import (invalid_models + valid_models), validate: true
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
assert_nil invalid_models[0].created_at
|
|
282
|
+
assert_nil invalid_models[0].updated_at
|
|
283
|
+
assert_nil invalid_models[1].created_at
|
|
284
|
+
assert_nil invalid_models[1].updated_at
|
|
285
|
+
|
|
286
|
+
assert_equal valid_models[0].created_at, Topic.all[0].created_at
|
|
287
|
+
assert_equal valid_models[0].updated_at, Topic.all[0].updated_at
|
|
288
|
+
assert_equal valid_models[1].created_at, Topic.all[1].created_at
|
|
289
|
+
assert_equal valid_models[1].updated_at, Topic.all[1].updated_at
|
|
290
|
+
end
|
|
87
291
|
end
|
|
88
292
|
|
|
89
293
|
it "should import valid data when mixed with invalid data" do
|
|
90
294
|
assert_difference "Topic.count", +2 do
|
|
91
|
-
|
|
295
|
+
Topic.import columns, valid_values + invalid_values, validate: true
|
|
92
296
|
end
|
|
93
297
|
assert_equal 0, Topic.where(title: invalid_values.map(&:first)).count
|
|
94
298
|
end
|
|
299
|
+
|
|
300
|
+
it "should run callbacks" do
|
|
301
|
+
assert_no_difference "Topic.count" do
|
|
302
|
+
Topic.import columns, [["invalid", "Jerry Carter"]], validate: true
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "should call validation methods" do
|
|
307
|
+
assert_no_difference "Topic.count" do
|
|
308
|
+
Topic.import columns, [["validate_failed", "Jerry Carter"]], validate: true
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
context "with uniqueness validators included" do
|
|
314
|
+
it "should not import duplicate records" do
|
|
315
|
+
Topic.import columns, valid_values
|
|
316
|
+
assert_no_difference "Topic.count" do
|
|
317
|
+
Topic.import columns, valid_values, validate_uniqueness: true
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
context "when validatoring presence of belongs_to association" do
|
|
323
|
+
it "should not import records without foreign key" do
|
|
324
|
+
assert_no_difference "UserToken.count" do
|
|
325
|
+
UserToken.import [:token], [['12345abcdef67890']]
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it "should import records with foreign key" do
|
|
330
|
+
assert_difference "UserToken.count", +1 do
|
|
331
|
+
UserToken.import [:user_name, :token], [%w("Bob", "12345abcdef67890")]
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it "should not mutate the defined validations" do
|
|
336
|
+
UserToken.import [:user_name, :token], [%w("Bob", "12345abcdef67890")]
|
|
337
|
+
ut = UserToken.new
|
|
338
|
+
ut.valid?
|
|
339
|
+
assert_includes ut.errors.messages, :user
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
context "without :validation option" do
|
|
345
|
+
let(:columns) { %w(title author_name) }
|
|
346
|
+
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
|
347
|
+
|
|
348
|
+
it "should not import invalid data" do
|
|
349
|
+
assert_no_difference "Topic.count" do
|
|
350
|
+
result = Topic.import columns, invalid_values
|
|
351
|
+
assert_equal 2, result.failed_instances.size
|
|
352
|
+
end
|
|
95
353
|
end
|
|
96
354
|
end
|
|
97
355
|
|
|
98
356
|
context "with :all_or_none option" do
|
|
99
357
|
let(:columns) { %w(title author_name) }
|
|
100
|
-
let(:valid_values) { [[
|
|
101
|
-
let(:invalid_values) { [[
|
|
358
|
+
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
|
359
|
+
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
|
102
360
|
let(:mixed_values) { valid_values + invalid_values }
|
|
103
361
|
|
|
104
362
|
context "with validation checks turned on" do
|
|
105
363
|
it "should import valid data" do
|
|
106
364
|
assert_difference "Topic.count", +2 do
|
|
107
|
-
|
|
365
|
+
Topic.import columns, valid_values, all_or_none: true
|
|
108
366
|
end
|
|
109
367
|
end
|
|
110
368
|
|
|
111
369
|
it "should not import invalid data" do
|
|
112
370
|
assert_no_difference "Topic.count" do
|
|
113
|
-
|
|
371
|
+
Topic.import columns, invalid_values, all_or_none: true
|
|
114
372
|
end
|
|
115
373
|
end
|
|
116
374
|
|
|
117
375
|
it "should not import valid data when mixed with invalid data" do
|
|
118
376
|
assert_no_difference "Topic.count" do
|
|
119
|
-
|
|
377
|
+
Topic.import columns, mixed_values, all_or_none: true
|
|
120
378
|
end
|
|
121
379
|
end
|
|
122
380
|
|
|
123
381
|
it "should report the failed instances" do
|
|
124
|
-
results = Topic.import columns, mixed_values, :
|
|
382
|
+
results = Topic.import columns, mixed_values, all_or_none: true
|
|
125
383
|
assert_equal invalid_values.size, results.failed_instances.size
|
|
126
384
|
results.failed_instances.each { |e| assert_kind_of Topic, e }
|
|
127
385
|
end
|
|
128
386
|
|
|
129
387
|
it "should report the zero inserts" do
|
|
130
|
-
results = Topic.import columns, mixed_values, :
|
|
388
|
+
results = Topic.import columns, mixed_values, all_or_none: true
|
|
131
389
|
assert_equal 0, results.num_inserts
|
|
132
390
|
end
|
|
133
391
|
end
|
|
134
392
|
end
|
|
135
393
|
|
|
394
|
+
context "with :batch_size option" do
|
|
395
|
+
it "should import with a single insert" do
|
|
396
|
+
assert_difference "Topic.count", +10 do
|
|
397
|
+
result = Topic.import Build(10, :topics), batch_size: 10
|
|
398
|
+
assert_equal 1, result.num_inserts if Topic.supports_import?
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it "should import with multiple inserts" do
|
|
403
|
+
assert_difference "Topic.count", +10 do
|
|
404
|
+
result = Topic.import Build(10, :topics), batch_size: 4
|
|
405
|
+
assert_equal 3, result.num_inserts if Topic.supports_import?
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
136
410
|
context "with :synchronize option" do
|
|
137
411
|
context "synchronizing on new records" do
|
|
138
412
|
let(:new_topics) { Build(3, :topics) }
|
|
139
413
|
|
|
140
414
|
it "doesn't reload any data (doesn't work)" do
|
|
141
|
-
Topic.import new_topics, :
|
|
142
|
-
|
|
415
|
+
Topic.import new_topics, synchronize: new_topics
|
|
416
|
+
if Topic.supports_setting_primary_key_of_imported_objects?
|
|
417
|
+
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
|
418
|
+
else
|
|
419
|
+
assert new_topics.all?(&:new_record?), "No record should have been reloaded"
|
|
420
|
+
end
|
|
143
421
|
end
|
|
144
422
|
end
|
|
145
423
|
|
|
@@ -147,7 +425,7 @@ describe "#import" do
|
|
|
147
425
|
let(:new_topics) { Build(3, :topics) }
|
|
148
426
|
|
|
149
427
|
it "reloads data for existing in-memory instances" do
|
|
150
|
-
Topic.import(new_topics, :
|
|
428
|
+
Topic.import(new_topics, synchronize: new_topics, synchronize_keys: [:title] )
|
|
151
429
|
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
|
152
430
|
end
|
|
153
431
|
end
|
|
@@ -156,21 +434,21 @@ describe "#import" do
|
|
|
156
434
|
let(:new_topics) { Generate(3, :topics) }
|
|
157
435
|
|
|
158
436
|
it "reloads data for existing in-memory instances" do
|
|
159
|
-
new_topics.each
|
|
160
|
-
Topic.import(new_topics, :
|
|
437
|
+
new_topics.each(&:destroy)
|
|
438
|
+
Topic.import(new_topics, synchronize: new_topics, synchronize_keys: [:title] )
|
|
161
439
|
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
|
162
440
|
end
|
|
163
441
|
end
|
|
164
442
|
end
|
|
165
443
|
|
|
166
444
|
context "with an array of unsaved model instances" do
|
|
167
|
-
let(:topic) { Build(:topic, :
|
|
445
|
+
let(:topic) { Build(:topic, title: "The RSpec Book", author_name: "David Chelimsky") }
|
|
168
446
|
let(:topics) { Build(9, :topics) }
|
|
169
|
-
let(:invalid_topics){ Build(7, :invalid_topics)}
|
|
447
|
+
let(:invalid_topics) { Build(7, :invalid_topics) }
|
|
170
448
|
|
|
171
449
|
it "should import records based on those model's attributes" do
|
|
172
450
|
assert_difference "Topic.count", +9 do
|
|
173
|
-
|
|
451
|
+
Topic.import topics
|
|
174
452
|
end
|
|
175
453
|
|
|
176
454
|
Topic.import [topic]
|
|
@@ -178,7 +456,7 @@ describe "#import" do
|
|
|
178
456
|
end
|
|
179
457
|
|
|
180
458
|
it "should not overwrite existing records" do
|
|
181
|
-
topic = Generate(:topic, :
|
|
459
|
+
topic = Generate(:topic, title: "foobar")
|
|
182
460
|
assert_no_difference "Topic.count" do
|
|
183
461
|
begin
|
|
184
462
|
Topic.transaction do
|
|
@@ -196,13 +474,13 @@ describe "#import" do
|
|
|
196
474
|
context "with validation checks turned on" do
|
|
197
475
|
it "should import valid models" do
|
|
198
476
|
assert_difference "Topic.count", +9 do
|
|
199
|
-
|
|
477
|
+
Topic.import topics, validate: true
|
|
200
478
|
end
|
|
201
479
|
end
|
|
202
480
|
|
|
203
481
|
it "should not import invalid models" do
|
|
204
482
|
assert_no_difference "Topic.count" do
|
|
205
|
-
|
|
483
|
+
Topic.import invalid_topics, validate: true
|
|
206
484
|
end
|
|
207
485
|
end
|
|
208
486
|
end
|
|
@@ -210,7 +488,7 @@ describe "#import" do
|
|
|
210
488
|
context "with validation checks turned off" do
|
|
211
489
|
it "should import invalid models" do
|
|
212
490
|
assert_difference "Topic.count", +7 do
|
|
213
|
-
|
|
491
|
+
Topic.import invalid_topics, validate: false
|
|
214
492
|
end
|
|
215
493
|
end
|
|
216
494
|
end
|
|
@@ -221,7 +499,7 @@ describe "#import" do
|
|
|
221
499
|
|
|
222
500
|
it "should import records populating the supplied columns with the corresponding model instance attributes" do
|
|
223
501
|
assert_difference "Topic.count", +2 do
|
|
224
|
-
|
|
502
|
+
Topic.import [:author_name, :title], topics
|
|
225
503
|
end
|
|
226
504
|
|
|
227
505
|
# imported topics should be findable by their imported attributes
|
|
@@ -232,7 +510,7 @@ describe "#import" do
|
|
|
232
510
|
it "should not populate fields for columns not imported" do
|
|
233
511
|
topics.first.author_email_address = "zach.dennis@gmail.com"
|
|
234
512
|
assert_difference "Topic.count", +2 do
|
|
235
|
-
|
|
513
|
+
Topic.import [:author_name, :title], topics
|
|
236
514
|
end
|
|
237
515
|
|
|
238
516
|
assert !Topic.where(author_email_address: "zach.dennis@gmail.com").first
|
|
@@ -244,30 +522,38 @@ describe "#import" do
|
|
|
244
522
|
Topic.import [:id, :author_name, :title], [[99, "Bob Jones", "Topic 99"]]
|
|
245
523
|
assert_equal 99, Topic.last.id
|
|
246
524
|
end
|
|
525
|
+
|
|
526
|
+
it "ignores the recursive option" do
|
|
527
|
+
assert_difference "Topic.count", +1 do
|
|
528
|
+
Topic.import [:author_name, :title], [["David Chelimsky", "The RSpec Book"]], recursive: true
|
|
529
|
+
end
|
|
530
|
+
end
|
|
247
531
|
end
|
|
248
532
|
|
|
249
533
|
context "ActiveRecord timestamps" do
|
|
534
|
+
let(:time) { Chronic.parse("5 minutes ago") }
|
|
535
|
+
|
|
250
536
|
context "when the timestamps columns are present" do
|
|
251
537
|
setup do
|
|
252
|
-
@existing_book = Book.create(title: "Fell", author_name: "Curry", publisher: "Bayer", created_at: 2.years.ago.utc, created_on: 2.years.ago.utc)
|
|
538
|
+
@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)
|
|
253
539
|
ActiveRecord::Base.default_timezone = :utc
|
|
254
|
-
Timecop.freeze
|
|
540
|
+
Timecop.freeze(time) do
|
|
255
541
|
assert_difference "Book.count", +2 do
|
|
256
|
-
|
|
542
|
+
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]]
|
|
257
543
|
end
|
|
258
544
|
end
|
|
259
545
|
@new_book, @existing_book = Book.last 2
|
|
260
546
|
end
|
|
261
547
|
|
|
262
|
-
it "should set the created_at column for new records"
|
|
263
|
-
|
|
548
|
+
it "should set the created_at column for new records" do
|
|
549
|
+
assert_in_delta time.to_i, @new_book.created_at.to_i, 1.second
|
|
264
550
|
end
|
|
265
551
|
|
|
266
552
|
it "should set the created_on column for new records" do
|
|
267
|
-
|
|
553
|
+
assert_in_delta time.to_i, @new_book.created_on.to_i, 1.second
|
|
268
554
|
end
|
|
269
555
|
|
|
270
|
-
it "should not set the created_at column for existing records"
|
|
556
|
+
it "should not set the created_at column for existing records" do
|
|
271
557
|
assert_equal 2.years.ago.utc.strftime("%Y:%d"), @existing_book.created_at.strftime("%Y:%d")
|
|
272
558
|
end
|
|
273
559
|
|
|
@@ -276,27 +562,42 @@ describe "#import" do
|
|
|
276
562
|
end
|
|
277
563
|
|
|
278
564
|
it "should set the updated_at column for new records" do
|
|
279
|
-
|
|
565
|
+
assert_in_delta time.to_i, @new_book.updated_at.to_i, 1.second
|
|
280
566
|
end
|
|
281
567
|
|
|
282
568
|
it "should set the updated_on column for new records" do
|
|
283
|
-
|
|
569
|
+
assert_in_delta time.to_i, @new_book.updated_on.to_i, 1.second
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
it "should not set the updated_at column for existing records" do
|
|
573
|
+
assert_equal 2.years.ago.utc.strftime("%Y:%d"), @existing_book.updated_at.strftime("%Y:%d")
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
it "should not set the updated_on column for existing records" do
|
|
577
|
+
assert_equal 2.years.ago.utc.strftime("%Y:%d"), @existing_book.updated_on.strftime("%Y:%d")
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
it "should not set the updated_at column on models if changed" do
|
|
581
|
+
timestamp = Time.now.utc
|
|
582
|
+
books = [
|
|
583
|
+
Book.new(author_name: "Foo", title: "Baz", created_at: timestamp, updated_at: timestamp)
|
|
584
|
+
]
|
|
585
|
+
Book.import books
|
|
586
|
+
assert_equal timestamp.strftime("%Y:%d"), Book.last.updated_at.strftime("%Y:%d")
|
|
284
587
|
end
|
|
285
588
|
end
|
|
286
589
|
|
|
287
590
|
context "when a custom time zone is set" do
|
|
288
|
-
let(:time){ Chronic.parse("5 minutes ago") }
|
|
289
|
-
|
|
290
591
|
setup do
|
|
291
592
|
Timecop.freeze(time) do
|
|
292
593
|
assert_difference "Book.count", +1 do
|
|
293
|
-
|
|
594
|
+
Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
|
|
294
595
|
end
|
|
295
596
|
end
|
|
296
597
|
@book = Book.last
|
|
297
598
|
end
|
|
298
599
|
|
|
299
|
-
it "should set the created_at and created_on timestamps for new records"
|
|
600
|
+
it "should set the created_at and created_on timestamps for new records" do
|
|
300
601
|
assert_in_delta time.to_i, @book.created_at.to_i, 1.second
|
|
301
602
|
assert_in_delta time.to_i, @book.created_on.to_i, 1.second
|
|
302
603
|
end
|
|
@@ -309,11 +610,11 @@ describe "#import" do
|
|
|
309
610
|
end
|
|
310
611
|
|
|
311
612
|
context "importing with database reserved words" do
|
|
312
|
-
let(:group) { Build(:group, :
|
|
613
|
+
let(:group) { Build(:group, order: "superx") }
|
|
313
614
|
|
|
314
615
|
it "should import just fine" do
|
|
315
616
|
assert_difference "Group.count", +1 do
|
|
316
|
-
|
|
617
|
+
Group.import [group]
|
|
317
618
|
end
|
|
318
619
|
assert_equal "superx", Group.first.order
|
|
319
620
|
end
|
|
@@ -327,72 +628,156 @@ describe "#import" do
|
|
|
327
628
|
end
|
|
328
629
|
|
|
329
630
|
context "importing through an association scope" do
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
631
|
+
{ has_many: :chapters, polymorphic: :discounts }.each do |association_type, association|
|
|
632
|
+
book = FactoryBot.create :book
|
|
633
|
+
scope = book.public_send association
|
|
634
|
+
klass = { chapters: Chapter, discounts: Discount }[association]
|
|
635
|
+
column = { chapters: :title, discounts: :amount }[association]
|
|
636
|
+
val1 = { chapters: 'A', discounts: 5 }[association]
|
|
637
|
+
val2 = { chapters: 'B', discounts: 6 }[association]
|
|
638
|
+
|
|
639
|
+
context "for #{association_type}" do
|
|
640
|
+
it "works importing models" do
|
|
641
|
+
scope.import [
|
|
642
|
+
klass.new(column => val1),
|
|
643
|
+
klass.new(column => val2)
|
|
644
|
+
]
|
|
645
|
+
|
|
646
|
+
assert_equal [val1, val2], scope.map(&column).sort
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
it "works importing array of columns and values" do
|
|
650
|
+
scope.import [column], [[val1], [val2]]
|
|
651
|
+
|
|
652
|
+
assert_equal [val1, val2], scope.map(&column).sort
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
it "works importing array of hashes" do
|
|
656
|
+
scope.import [{ column => val1 }, { column => val2 }]
|
|
657
|
+
|
|
658
|
+
assert_equal [val1, val2], scope.map(&column).sort
|
|
338
659
|
end
|
|
339
660
|
end
|
|
340
|
-
end
|
|
341
661
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
Book.new(:author_name => "Author #1", :title => "Book #1"),
|
|
346
|
-
Book.new(:author_name => "Author #2", :title => "Book #2"),
|
|
347
|
-
]
|
|
348
|
-
topic.books.import books
|
|
349
|
-
assert_equal 2, topic.books.count
|
|
350
|
-
assert topic.books.detect { |b| b.title == "Book #1" && b.author_name == "Author #1" }
|
|
351
|
-
assert topic.books.detect { |b| b.title == "Book #2" && b.author_name == "Author #2" }
|
|
352
|
-
end
|
|
662
|
+
it "works with a non-standard association primary key" do
|
|
663
|
+
user = User.create(id: 1, name: 'Solomon')
|
|
664
|
+
user.user_tokens.import [:id, :token], [[5, '12345abcdef67890']]
|
|
353
665
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
Book.new(:author_name => "Foo", :title => "Baz"),
|
|
358
|
-
Book.new(:author_name => "Foo2", :title => "Baz2"),
|
|
359
|
-
]
|
|
360
|
-
topic.books.import [:author_name, :title], [["Author #1", "Book #1"], ["Author #2", "Book #2"]]
|
|
361
|
-
assert_equal 2, topic.books.count
|
|
362
|
-
assert topic.books.detect { |b| b.title == "Book #1" && b.author_name == "Author #1" }
|
|
363
|
-
assert topic.books.detect { |b| b.title == "Book #2" && b.author_name == "Author #2" }
|
|
666
|
+
token = UserToken.find(5)
|
|
667
|
+
assert_equal 'Solomon', token.user_name
|
|
668
|
+
end
|
|
364
669
|
end
|
|
670
|
+
end
|
|
365
671
|
|
|
672
|
+
context "importing model with polymorphic belongs_to" do
|
|
673
|
+
it "works without error" do
|
|
674
|
+
book = FactoryBot.create :book
|
|
675
|
+
discount = Discount.new(discountable: book)
|
|
676
|
+
|
|
677
|
+
Discount.import([discount])
|
|
678
|
+
|
|
679
|
+
assert_equal 1, Discount.count
|
|
680
|
+
end
|
|
366
681
|
end
|
|
367
682
|
|
|
368
683
|
context 'When importing models with Enum fields' do
|
|
369
684
|
it 'should be able to import enum fields' do
|
|
370
685
|
Book.delete_all if Book.count > 0
|
|
371
686
|
books = [
|
|
372
|
-
Book.new(:
|
|
373
|
-
Book.new(:
|
|
687
|
+
Book.new(author_name: "Foo", title: "Baz", status: 0),
|
|
688
|
+
Book.new(author_name: "Foo2", title: "Baz2", status: 1),
|
|
374
689
|
]
|
|
375
690
|
Book.import books
|
|
376
691
|
assert_equal 2, Book.count
|
|
377
|
-
|
|
378
|
-
|
|
692
|
+
|
|
693
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
|
694
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
|
695
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
|
696
|
+
else
|
|
697
|
+
assert_equal 0, Book.first.read_attribute('status')
|
|
698
|
+
assert_equal 1, Book.last.read_attribute('status')
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
it 'should be able to import enum fields with default value' do
|
|
703
|
+
Book.delete_all if Book.count > 0
|
|
704
|
+
books = [
|
|
705
|
+
Book.new(author_name: "Foo", title: "Baz")
|
|
706
|
+
]
|
|
707
|
+
Book.import books
|
|
708
|
+
assert_equal 1, Book.count
|
|
709
|
+
|
|
710
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
|
711
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
|
712
|
+
else
|
|
713
|
+
assert_equal 0, Book.first.read_attribute('status')
|
|
714
|
+
end
|
|
379
715
|
end
|
|
380
716
|
|
|
381
|
-
if ENV['AR_VERSION'].
|
|
717
|
+
if ENV['AR_VERSION'].to_f > 4.1
|
|
382
718
|
it 'should be able to import enum fields by name' do
|
|
383
719
|
Book.delete_all if Book.count > 0
|
|
384
720
|
books = [
|
|
385
|
-
Book.new(:
|
|
386
|
-
Book.new(:
|
|
721
|
+
Book.new(author_name: "Foo", title: "Baz", status: :draft),
|
|
722
|
+
Book.new(author_name: "Foo2", title: "Baz2", status: :published),
|
|
387
723
|
]
|
|
388
724
|
Book.import books
|
|
389
725
|
assert_equal 2, Book.count
|
|
726
|
+
|
|
727
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
|
728
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
|
729
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
|
730
|
+
else
|
|
731
|
+
assert_equal 0, Book.first.read_attribute('status')
|
|
732
|
+
assert_equal 1, Book.last.read_attribute('status')
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
context 'When importing arrays of values with Enum fields' do
|
|
739
|
+
let(:columns) { [:author_name, :title, :status] }
|
|
740
|
+
let(:values) { [['Author #1', 'Book #1', 0], ['Author #2', 'Book #2', 1]] }
|
|
741
|
+
|
|
742
|
+
it 'should be able to import enum fields' do
|
|
743
|
+
Book.delete_all if Book.count > 0
|
|
744
|
+
Book.import columns, values
|
|
745
|
+
assert_equal 2, Book.count
|
|
746
|
+
|
|
747
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
|
748
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
|
749
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
|
750
|
+
else
|
|
390
751
|
assert_equal 0, Book.first.read_attribute('status')
|
|
391
752
|
assert_equal 1, Book.last.read_attribute('status')
|
|
392
753
|
end
|
|
393
754
|
end
|
|
394
755
|
end
|
|
395
756
|
|
|
757
|
+
context 'importing arrays of values with boolean fields' do
|
|
758
|
+
let(:columns) { [:author_name, :title, :for_sale] }
|
|
759
|
+
|
|
760
|
+
it 'should be able to coerce integers as boolean fields' do
|
|
761
|
+
Book.delete_all if Book.count > 0
|
|
762
|
+
values = [['Author #1', 'Book #1', 0], ['Author #2', 'Book #2', 1]]
|
|
763
|
+
assert_difference "Book.count", +2 do
|
|
764
|
+
Book.import columns, values
|
|
765
|
+
end
|
|
766
|
+
assert_equal false, Book.first.for_sale
|
|
767
|
+
assert_equal true, Book.last.for_sale
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
it 'should be able to coerce strings as boolean fields' do
|
|
771
|
+
Book.delete_all if Book.count > 0
|
|
772
|
+
values = [['Author #1', 'Book #1', 'false'], ['Author #2', 'Book #2', 'true']]
|
|
773
|
+
assert_difference "Book.count", +2 do
|
|
774
|
+
Book.import columns, values
|
|
775
|
+
end
|
|
776
|
+
assert_equal false, Book.first.for_sale
|
|
777
|
+
assert_equal true, Book.last.for_sale
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
|
|
396
781
|
describe "importing when model has default_scope" do
|
|
397
782
|
it "doesn't import the default scope values" do
|
|
398
783
|
assert_difference "Widget.unscoped.count", +2 do
|
|
@@ -413,11 +798,145 @@ describe "#import" do
|
|
|
413
798
|
end
|
|
414
799
|
|
|
415
800
|
describe "importing serialized fields" do
|
|
801
|
+
it "imports values for serialized Hash fields" do
|
|
802
|
+
assert_difference "Widget.unscoped.count", +1 do
|
|
803
|
+
Widget.import [:w_id, :data], [[1, { a: :b }]]
|
|
804
|
+
end
|
|
805
|
+
assert_equal({ a: :b }, Widget.find_by_w_id(1).data)
|
|
806
|
+
end
|
|
807
|
+
|
|
416
808
|
it "imports values for serialized fields" do
|
|
417
809
|
assert_difference "Widget.unscoped.count", +1 do
|
|
418
|
-
Widget.import [:w_id, :
|
|
810
|
+
Widget.import [:w_id, :unspecified_data], [[1, { a: :b }]]
|
|
811
|
+
end
|
|
812
|
+
assert_equal({ a: :b }, Widget.find_by_w_id(1).unspecified_data)
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
it "imports values for custom coder" do
|
|
816
|
+
assert_difference "Widget.unscoped.count", +1 do
|
|
817
|
+
Widget.import [:w_id, :custom_data], [[1, { a: :b }]]
|
|
818
|
+
end
|
|
819
|
+
assert_equal({ a: :b }, Widget.find_by_w_id(1).custom_data)
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
let(:data) { { a: :b } }
|
|
823
|
+
it "imports values for serialized JSON fields" do
|
|
824
|
+
assert_difference "Widget.unscoped.count", +1 do
|
|
825
|
+
Widget.import [:w_id, :json_data], [[9, data]]
|
|
826
|
+
end
|
|
827
|
+
assert_equal(data.as_json, Widget.find_by_w_id(9).json_data)
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
it "imports serialized values from saved records" do
|
|
831
|
+
Widget.import [:w_id, :json_data], [[1, data]]
|
|
832
|
+
assert_equal data.as_json, Widget.last.json_data
|
|
833
|
+
|
|
834
|
+
w = Widget.last
|
|
835
|
+
w.w_id = 2
|
|
836
|
+
Widget.import([w])
|
|
837
|
+
assert_equal data.as_json, Widget.last.json_data
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
context "with a store" do
|
|
841
|
+
it "imports serialized attributes set using accessors" do
|
|
842
|
+
vendors = [Vendor.new(name: 'Vendor 1', color: 'blue')]
|
|
843
|
+
assert_difference "Vendor.count", +1 do
|
|
844
|
+
Vendor.import vendors
|
|
845
|
+
end
|
|
846
|
+
assert_equal('blue', Vendor.first.color)
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
describe "#import!" do
|
|
852
|
+
context "with an array of unsaved model instances" do
|
|
853
|
+
let(:topics) { Build(2, :topics) }
|
|
854
|
+
let(:invalid_topics) { Build(2, :invalid_topics) }
|
|
855
|
+
|
|
856
|
+
context "with invalid data" do
|
|
857
|
+
it "should raise ActiveRecord::RecordInvalid" do
|
|
858
|
+
assert_no_difference "Topic.count" do
|
|
859
|
+
assert_raise ActiveRecord::RecordInvalid do
|
|
860
|
+
Topic.import! invalid_topics
|
|
861
|
+
end
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
context "with valid data" do
|
|
867
|
+
it "should import data" do
|
|
868
|
+
assert_difference "Topic.count", +2 do
|
|
869
|
+
Topic.import! topics
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
context "with array of columns and array of values" do
|
|
876
|
+
let(:columns) { %w(title author_name) }
|
|
877
|
+
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
|
878
|
+
let(:invalid_values) { [["Rails Recipes", "Chad Fowler"], ["The RSpec Book", ""], ["Agile+UX", ""]] }
|
|
879
|
+
|
|
880
|
+
context "with invalid data" do
|
|
881
|
+
it "should raise ActiveRecord::RecordInvalid" do
|
|
882
|
+
assert_no_difference "Topic.count" do
|
|
883
|
+
assert_raise ActiveRecord::RecordInvalid do
|
|
884
|
+
Topic.import! columns, invalid_values
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
context "with valid data" do
|
|
891
|
+
it "should import data" do
|
|
892
|
+
assert_difference "Topic.count", +2 do
|
|
893
|
+
Topic.import! columns, valid_values
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
context "with objects that respond to .to_sql as values" do
|
|
900
|
+
let(:columns) { %w(title author_name) }
|
|
901
|
+
let(:valid_values) { [["LDAP", Book.select("'Jerry Carter'").limit(1)], ["Rails Recipes", Book.select("'Chad Fowler'").limit(1)]] }
|
|
902
|
+
|
|
903
|
+
it "should import data" do
|
|
904
|
+
assert_difference "Topic.count", +2 do
|
|
905
|
+
Topic.import! columns, valid_values
|
|
906
|
+
topics = Topic.all
|
|
907
|
+
assert_equal "Jerry Carter", topics.first.author_name
|
|
908
|
+
assert_equal "Chad Fowler", topics.last.author_name
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
end
|
|
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) }
|
|
419
939
|
end
|
|
420
|
-
assert_equal({:a => :b}, Widget.find_by_w_id(1).data)
|
|
421
940
|
end
|
|
422
941
|
end
|
|
423
942
|
end
|