activerecord-import 0.12.0 → 0.13.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/.rubocop.yml +49 -0
- data/.rubocop_todo.yml +36 -0
- data/.travis.yml +31 -7
- data/CHANGELOG.md +19 -0
- data/Gemfile +5 -2
- data/README.markdown +6 -1
- data/Rakefile +5 -2
- data/activerecord-import.gemspec +1 -1
- data/benchmarks/benchmark.rb +67 -68
- data/benchmarks/lib/base.rb +136 -137
- data/benchmarks/lib/cli_parser.rb +106 -107
- data/benchmarks/lib/mysql2_benchmark.rb +19 -21
- data/benchmarks/lib/output_to_csv.rb +2 -1
- data/benchmarks/lib/output_to_html.rb +8 -13
- data/benchmarks/schema/mysql_schema.rb +8 -8
- data/gemfiles/4.0.gemfile +1 -1
- data/gemfiles/4.1.gemfile +1 -1
- data/gemfiles/4.2.gemfile +1 -1
- data/gemfiles/5.0.gemfile +1 -1
- data/lib/activerecord-import.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +0 -1
- data/lib/activerecord-import/adapters/abstract_adapter.rb +9 -9
- data/lib/activerecord-import/adapters/mysql_adapter.rb +17 -17
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +20 -22
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +9 -9
- data/lib/activerecord-import/base.rb +3 -3
- data/lib/activerecord-import/import.rb +152 -131
- data/lib/activerecord-import/synchronize.rb +20 -20
- data/lib/activerecord-import/value_sets_parser.rb +7 -6
- data/lib/activerecord-import/version.rb +1 -1
- data/test/adapters/mysql2spatial.rb +1 -1
- data/test/adapters/postgis.rb +1 -1
- data/test/adapters/postgresql.rb +1 -1
- data/test/adapters/spatialite.rb +1 -1
- data/test/adapters/sqlite3.rb +1 -1
- data/test/import_test.rb +121 -70
- data/test/models/book.rb +5 -6
- data/test/models/chapter.rb +2 -2
- data/test/models/discount.rb +3 -0
- data/test/models/end_note.rb +2 -2
- data/test/models/promotion.rb +1 -1
- data/test/models/question.rb +1 -1
- data/test/models/rule.rb +2 -2
- data/test/models/topic.rb +3 -3
- data/test/models/widget.rb +1 -1
- data/test/postgis/import_test.rb +1 -1
- data/test/schema/generic_schema.rb +100 -96
- data/test/schema/mysql_schema.rb +5 -7
- data/test/sqlite3/import_test.rb +0 -2
- data/test/support/active_support/test_case_extensions.rb +12 -15
- data/test/support/assertions.rb +1 -1
- data/test/support/factories.rb +15 -16
- data/test/support/generate.rb +4 -4
- data/test/support/mysql/import_examples.rb +21 -21
- data/test/support/postgresql/import_examples.rb +83 -55
- data/test/support/shared_examples/on_duplicate_key_update.rb +23 -23
- data/test/synchronize_test.rb +2 -2
- data/test/test_helper.rb +6 -8
- data/test/value_sets_bytes_parser_test.rb +14 -17
- data/test/value_sets_records_parser_test.rb +6 -6
- metadata +7 -4
- data/test/travis/build.sh +0 -34
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord # :nodoc:
|
2
2
|
class Base # :nodoc:
|
3
|
-
|
4
3
|
# Synchronizes the passed in ActiveRecord instances with data
|
5
4
|
# from the database. This is like calling reload on an individual
|
6
5
|
# ActiveRecord instance but it is intended for use on multiple instances.
|
@@ -21,45 +20,46 @@ module ActiveRecord # :nodoc:
|
|
21
20
|
# Post.synchronize posts, [:name] # queries on the :name column and not the :id column
|
22
21
|
# posts.first.address # => "1245 Foo Ln" instead of whatever it was
|
23
22
|
#
|
24
|
-
def self.synchronize(instances, keys=[
|
23
|
+
def self.synchronize(instances, keys = [primary_key])
|
25
24
|
return if instances.empty?
|
26
25
|
|
27
26
|
conditions = {}
|
28
|
-
order = ""
|
29
27
|
|
30
|
-
key_values = keys.map { |key| instances.map(&
|
28
|
+
key_values = keys.map { |key| instances.map(&key.to_sym) }
|
31
29
|
keys.zip(key_values).each { |key, values| conditions[key] = values }
|
32
|
-
order = keys.map{ |key| "#{key} ASC" }.join(",")
|
30
|
+
order = keys.map { |key| "#{key} ASC" }.join(",")
|
33
31
|
|
34
32
|
klass = instances.first.class
|
35
33
|
|
36
34
|
fresh_instances = klass.where(conditions).order(order)
|
37
35
|
instances.each do |instance|
|
38
36
|
matched_instance = fresh_instances.detect do |fresh_instance|
|
39
|
-
keys.all?{ |key| fresh_instance.send(key) == instance.send(key) }
|
37
|
+
keys.all? { |key| fresh_instance.send(key) == instance.send(key) }
|
40
38
|
end
|
41
39
|
|
42
|
-
|
43
|
-
instance.send :clear_aggregation_cache
|
44
|
-
instance.send :clear_association_cache
|
45
|
-
instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
|
40
|
+
next unless matched_instance
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
instance.changed_attributes.clear # Rails 3.1, 3.2
|
51
|
-
end
|
42
|
+
instance.send :clear_aggregation_cache
|
43
|
+
instance.send :clear_association_cache
|
44
|
+
instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
|
52
45
|
|
53
|
-
|
54
|
-
#
|
55
|
-
|
56
|
-
instance.instance_variable_set
|
46
|
+
if instance.respond_to?(:clear_changes_information)
|
47
|
+
instance.clear_changes_information # Rails 4.2 and higher
|
48
|
+
else
|
49
|
+
instance.instance_variable_set :@attributes_cache, {} # Rails 4.0, 4.1
|
50
|
+
instance.changed_attributes.clear # Rails 3.1, 3.2
|
51
|
+
instance.previous_changes.clear
|
57
52
|
end
|
53
|
+
|
54
|
+
# Since the instance now accurately reflects the record in
|
55
|
+
# the database, ensure that instance.persisted? is true.
|
56
|
+
instance.instance_variable_set '@new_record', false
|
57
|
+
instance.instance_variable_set '@destroyed', false
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
# See ActiveRecord::ConnectionAdapters::AbstractAdapter.synchronize
|
62
|
-
def synchronize(instances, key=[ActiveRecord::Base.primary_key])
|
62
|
+
def synchronize(instances, key = [ActiveRecord::Base.primary_key])
|
63
63
|
self.class.synchronize(instances, key)
|
64
64
|
end
|
65
65
|
end
|
@@ -14,8 +14,9 @@ module ActiveRecord::Import
|
|
14
14
|
|
15
15
|
def parse
|
16
16
|
value_sets = []
|
17
|
-
arr
|
18
|
-
|
17
|
+
arr = []
|
18
|
+
current_size = 0
|
19
|
+
values.each_with_index do |val, i|
|
19
20
|
comma_bytes = arr.size
|
20
21
|
bytes_thus_far = reserved_bytes + current_size + val.bytesize + comma_bytes
|
21
22
|
if bytes_thus_far <= max_bytes
|
@@ -23,15 +24,15 @@ module ActiveRecord::Import
|
|
23
24
|
arr << val
|
24
25
|
else
|
25
26
|
value_sets << arr
|
26
|
-
arr = [
|
27
|
+
arr = [val]
|
27
28
|
current_size = val.bytesize
|
28
29
|
end
|
29
30
|
|
30
31
|
# if we're on the last iteration push whatever we have in arr to value_sets
|
31
|
-
value_sets << arr if i == (values.size-1)
|
32
|
+
value_sets << arr if i == (values.size - 1)
|
32
33
|
end
|
33
34
|
|
34
|
-
[
|
35
|
+
[*value_sets]
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
@@ -48,7 +49,7 @@ module ActiveRecord::Import
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def parse
|
51
|
-
@values.in_groups_of(max_records,
|
52
|
+
@values.in_groups_of(max_records, false)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
@@ -1 +1 @@
|
|
1
|
-
ENV["ARE_DB"] = "mysql2spatial"
|
1
|
+
ENV["ARE_DB"] = "mysql2spatial"
|
data/test/adapters/postgis.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ENV["ARE_DB"] = "postgis"
|
1
|
+
ENV["ARE_DB"] = "postgis"
|
data/test/adapters/postgresql.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ENV["ARE_DB"] = "postgresql"
|
1
|
+
ENV["ARE_DB"] = "postgresql"
|
data/test/adapters/spatialite.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ENV["ARE_DB"] = "spatialite"
|
1
|
+
ENV["ARE_DB"] = "spatialite"
|
data/test/adapters/sqlite3.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ENV["ARE_DB"] = "sqlite3"
|
1
|
+
ENV["ARE_DB"] = "sqlite3"
|
data/test/import_test.rb
CHANGED
@@ -23,13 +23,13 @@ describe "#import" do
|
|
23
23
|
it "should not modify the passed in columns array" do
|
24
24
|
assert_nothing_raised do
|
25
25
|
columns = %w(title author_name).freeze
|
26
|
-
Topic.import columns, [
|
26
|
+
Topic.import columns, [%w(foo bar)]
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
30
|
it "should not modify the passed in values array" do
|
31
31
|
assert_nothing_raised do
|
32
|
-
values = [
|
32
|
+
values = [%w(foo bar)].freeze
|
33
33
|
Topic.import %w(title author_name), values
|
34
34
|
end
|
35
35
|
end
|
@@ -47,26 +47,26 @@ describe "#import" do
|
|
47
47
|
|
48
48
|
context "with :validation option" do
|
49
49
|
let(:columns) { %w(title author_name) }
|
50
|
-
let(:valid_values) { [[
|
51
|
-
let(:valid_values_with_context) { [[
|
52
|
-
let(:invalid_values) { [[
|
50
|
+
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
51
|
+
let(:valid_values_with_context) { [[1111, "Jerry Carter"], [2222, "Chad Fowler"]] }
|
52
|
+
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
53
53
|
|
54
54
|
context "with validation checks turned off" do
|
55
55
|
it "should import valid data" do
|
56
56
|
assert_difference "Topic.count", +2 do
|
57
|
-
|
57
|
+
Topic.import columns, valid_values, validate: false
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
it "should import invalid data" do
|
62
62
|
assert_difference "Topic.count", +2 do
|
63
|
-
|
63
|
+
Topic.import columns, invalid_values, validate: false
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
67
|
it 'should raise a specific error if a column does not exist' do
|
68
68
|
assert_raises ActiveRecord::Import::MissingColumnError do
|
69
|
-
Topic.import ['foo'], [['bar']], :
|
69
|
+
Topic.import ['foo'], [['bar']], validate: false
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -74,37 +74,37 @@ describe "#import" do
|
|
74
74
|
context "with validation checks turned on" do
|
75
75
|
it "should import valid data" do
|
76
76
|
assert_difference "Topic.count", +2 do
|
77
|
-
|
77
|
+
Topic.import columns, valid_values, validate: true
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
it "should import valid data with on option" do
|
82
82
|
assert_difference "Topic.count", +2 do
|
83
|
-
|
83
|
+
Topic.import columns, valid_values_with_context, validate_with_context: :context_test
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
it "should not import invalid data" do
|
88
88
|
assert_no_difference "Topic.count" do
|
89
|
-
|
89
|
+
Topic.import columns, invalid_values, validate: true
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
93
|
it "should import invalid data with on option" do
|
94
94
|
assert_no_difference "Topic.count" do
|
95
|
-
|
95
|
+
Topic.import columns, valid_values, validate_with_context: :context_test
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
99
|
it "should report the failed instances" do
|
100
|
-
results = Topic.import columns, invalid_values, :
|
100
|
+
results = Topic.import columns, invalid_values, validate: true
|
101
101
|
assert_equal invalid_values.size, results.failed_instances.size
|
102
|
-
results.failed_instances.each{ |e| assert_kind_of Topic, e }
|
102
|
+
results.failed_instances.each { |e| assert_kind_of Topic, e }
|
103
103
|
end
|
104
104
|
|
105
105
|
it "should import valid data when mixed with invalid data" do
|
106
106
|
assert_difference "Topic.count", +2 do
|
107
|
-
|
107
|
+
Topic.import columns, valid_values + invalid_values, validate: true
|
108
108
|
end
|
109
109
|
assert_equal 0, Topic.where(title: invalid_values.map(&:first)).count
|
110
110
|
end
|
@@ -113,48 +113,64 @@ describe "#import" do
|
|
113
113
|
|
114
114
|
context "with :all_or_none option" do
|
115
115
|
let(:columns) { %w(title author_name) }
|
116
|
-
let(:valid_values) { [[
|
117
|
-
let(:invalid_values) { [[
|
116
|
+
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
117
|
+
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
118
118
|
let(:mixed_values) { valid_values + invalid_values }
|
119
119
|
|
120
120
|
context "with validation checks turned on" do
|
121
121
|
it "should import valid data" do
|
122
122
|
assert_difference "Topic.count", +2 do
|
123
|
-
|
123
|
+
Topic.import columns, valid_values, all_or_none: true
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
127
|
it "should not import invalid data" do
|
128
128
|
assert_no_difference "Topic.count" do
|
129
|
-
|
129
|
+
Topic.import columns, invalid_values, all_or_none: true
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
133
|
it "should not import valid data when mixed with invalid data" do
|
134
134
|
assert_no_difference "Topic.count" do
|
135
|
-
|
135
|
+
Topic.import columns, mixed_values, all_or_none: true
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
139
|
it "should report the failed instances" do
|
140
|
-
results = Topic.import columns, mixed_values, :
|
140
|
+
results = Topic.import columns, mixed_values, all_or_none: true
|
141
141
|
assert_equal invalid_values.size, results.failed_instances.size
|
142
142
|
results.failed_instances.each { |e| assert_kind_of Topic, e }
|
143
143
|
end
|
144
144
|
|
145
145
|
it "should report the zero inserts" do
|
146
|
-
results = Topic.import columns, mixed_values, :
|
146
|
+
results = Topic.import columns, mixed_values, all_or_none: true
|
147
147
|
assert_equal 0, results.num_inserts
|
148
148
|
end
|
149
149
|
end
|
150
150
|
end
|
151
151
|
|
152
|
+
context "with :batch_size option" do
|
153
|
+
it "should import with a single insert" do
|
154
|
+
assert_difference "Topic.count", +10 do
|
155
|
+
result = Topic.import Build(10, :topics), batch_size: 10
|
156
|
+
assert_equal 1, result.num_inserts if Topic.supports_import?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should import with multiple inserts" do
|
161
|
+
assert_difference "Topic.count", +10 do
|
162
|
+
result = Topic.import Build(10, :topics), batch_size: 4
|
163
|
+
assert_equal 3, result.num_inserts if Topic.supports_import?
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
152
168
|
context "with :synchronize option" do
|
153
169
|
context "synchronizing on new records" do
|
154
170
|
let(:new_topics) { Build(3, :topics) }
|
155
171
|
|
156
172
|
it "doesn't reload any data (doesn't work)" do
|
157
|
-
Topic.import new_topics, :
|
173
|
+
Topic.import new_topics, synchronize: new_topics
|
158
174
|
if Topic.support_setting_primary_key_of_imported_objects?
|
159
175
|
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
160
176
|
else
|
@@ -167,7 +183,7 @@ describe "#import" do
|
|
167
183
|
let(:new_topics) { Build(3, :topics) }
|
168
184
|
|
169
185
|
it "reloads data for existing in-memory instances" do
|
170
|
-
Topic.import(new_topics, :
|
186
|
+
Topic.import(new_topics, synchronize: new_topics, synchronize_keys: [:title] )
|
171
187
|
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
172
188
|
end
|
173
189
|
end
|
@@ -176,21 +192,21 @@ describe "#import" do
|
|
176
192
|
let(:new_topics) { Generate(3, :topics) }
|
177
193
|
|
178
194
|
it "reloads data for existing in-memory instances" do
|
179
|
-
new_topics.each
|
180
|
-
Topic.import(new_topics, :
|
195
|
+
new_topics.each(&:destroy)
|
196
|
+
Topic.import(new_topics, synchronize: new_topics, synchronize_keys: [:title] )
|
181
197
|
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
182
198
|
end
|
183
199
|
end
|
184
200
|
end
|
185
201
|
|
186
202
|
context "with an array of unsaved model instances" do
|
187
|
-
let(:topic) { Build(:topic, :
|
203
|
+
let(:topic) { Build(:topic, title: "The RSpec Book", author_name: "David Chelimsky") }
|
188
204
|
let(:topics) { Build(9, :topics) }
|
189
|
-
let(:invalid_topics){ Build(7, :invalid_topics)}
|
205
|
+
let(:invalid_topics) { Build(7, :invalid_topics) }
|
190
206
|
|
191
207
|
it "should import records based on those model's attributes" do
|
192
208
|
assert_difference "Topic.count", +9 do
|
193
|
-
|
209
|
+
Topic.import topics
|
194
210
|
end
|
195
211
|
|
196
212
|
Topic.import [topic]
|
@@ -198,7 +214,7 @@ describe "#import" do
|
|
198
214
|
end
|
199
215
|
|
200
216
|
it "should not overwrite existing records" do
|
201
|
-
topic = Generate(:topic, :
|
217
|
+
topic = Generate(:topic, title: "foobar")
|
202
218
|
assert_no_difference "Topic.count" do
|
203
219
|
begin
|
204
220
|
Topic.transaction do
|
@@ -216,13 +232,13 @@ describe "#import" do
|
|
216
232
|
context "with validation checks turned on" do
|
217
233
|
it "should import valid models" do
|
218
234
|
assert_difference "Topic.count", +9 do
|
219
|
-
|
235
|
+
Topic.import topics, validate: true
|
220
236
|
end
|
221
237
|
end
|
222
238
|
|
223
239
|
it "should not import invalid models" do
|
224
240
|
assert_no_difference "Topic.count" do
|
225
|
-
|
241
|
+
Topic.import invalid_topics, validate: true
|
226
242
|
end
|
227
243
|
end
|
228
244
|
end
|
@@ -230,7 +246,7 @@ describe "#import" do
|
|
230
246
|
context "with validation checks turned off" do
|
231
247
|
it "should import invalid models" do
|
232
248
|
assert_difference "Topic.count", +7 do
|
233
|
-
|
249
|
+
Topic.import invalid_topics, validate: false
|
234
250
|
end
|
235
251
|
end
|
236
252
|
end
|
@@ -241,7 +257,7 @@ describe "#import" do
|
|
241
257
|
|
242
258
|
it "should import records populating the supplied columns with the corresponding model instance attributes" do
|
243
259
|
assert_difference "Topic.count", +2 do
|
244
|
-
|
260
|
+
Topic.import [:author_name, :title], topics
|
245
261
|
end
|
246
262
|
|
247
263
|
# imported topics should be findable by their imported attributes
|
@@ -252,7 +268,7 @@ describe "#import" do
|
|
252
268
|
it "should not populate fields for columns not imported" do
|
253
269
|
topics.first.author_email_address = "zach.dennis@gmail.com"
|
254
270
|
assert_difference "Topic.count", +2 do
|
255
|
-
|
271
|
+
Topic.import [:author_name, :title], topics
|
256
272
|
end
|
257
273
|
|
258
274
|
assert !Topic.where(author_email_address: "zach.dennis@gmail.com").first
|
@@ -267,27 +283,29 @@ describe "#import" do
|
|
267
283
|
end
|
268
284
|
|
269
285
|
context "ActiveRecord timestamps" do
|
286
|
+
let(:time) { Chronic.parse("5 minutes ago") }
|
287
|
+
|
270
288
|
context "when the timestamps columns are present" do
|
271
289
|
setup do
|
272
290
|
@existing_book = Book.create(title: "Fell", author_name: "Curry", publisher: "Bayer", created_at: 2.years.ago.utc, created_on: 2.years.ago.utc)
|
273
291
|
ActiveRecord::Base.default_timezone = :utc
|
274
|
-
Timecop.freeze
|
292
|
+
Timecop.freeze(time) do
|
275
293
|
assert_difference "Book.count", +2 do
|
276
|
-
|
294
|
+
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]]
|
277
295
|
end
|
278
296
|
end
|
279
297
|
@new_book, @existing_book = Book.last 2
|
280
298
|
end
|
281
299
|
|
282
|
-
it "should set the created_at column for new records"
|
283
|
-
|
300
|
+
it "should set the created_at column for new records" do
|
301
|
+
assert_in_delta time.to_i, @new_book.created_at.to_i, 1.second
|
284
302
|
end
|
285
303
|
|
286
304
|
it "should set the created_on column for new records" do
|
287
|
-
|
305
|
+
assert_in_delta time.to_i, @new_book.created_on.to_i, 1.second
|
288
306
|
end
|
289
307
|
|
290
|
-
it "should not set the created_at column for existing records"
|
308
|
+
it "should not set the created_at column for existing records" do
|
291
309
|
assert_equal 2.years.ago.utc.strftime("%Y:%d"), @existing_book.created_at.strftime("%Y:%d")
|
292
310
|
end
|
293
311
|
|
@@ -296,27 +314,25 @@ describe "#import" do
|
|
296
314
|
end
|
297
315
|
|
298
316
|
it "should set the updated_at column for new records" do
|
299
|
-
|
317
|
+
assert_in_delta time.to_i, @new_book.updated_at.to_i, 1.second
|
300
318
|
end
|
301
319
|
|
302
320
|
it "should set the updated_on column for new records" do
|
303
|
-
|
321
|
+
assert_in_delta time.to_i, @new_book.updated_on.to_i, 1.second
|
304
322
|
end
|
305
323
|
end
|
306
324
|
|
307
325
|
context "when a custom time zone is set" do
|
308
|
-
let(:time){ Chronic.parse("5 minutes ago") }
|
309
|
-
|
310
326
|
setup do
|
311
327
|
Timecop.freeze(time) do
|
312
328
|
assert_difference "Book.count", +1 do
|
313
|
-
|
329
|
+
Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
|
314
330
|
end
|
315
331
|
end
|
316
332
|
@book = Book.last
|
317
333
|
end
|
318
334
|
|
319
|
-
it "should set the created_at and created_on timestamps for new records"
|
335
|
+
it "should set the created_at and created_on timestamps for new records" do
|
320
336
|
assert_in_delta time.to_i, @book.created_at.to_i, 1.second
|
321
337
|
assert_in_delta time.to_i, @book.created_on.to_i, 1.second
|
322
338
|
end
|
@@ -329,11 +345,11 @@ describe "#import" do
|
|
329
345
|
end
|
330
346
|
|
331
347
|
context "importing with database reserved words" do
|
332
|
-
let(:group) { Build(:group, :
|
348
|
+
let(:group) { Build(:group, order: "superx") }
|
333
349
|
|
334
350
|
it "should import just fine" do
|
335
351
|
assert_difference "Group.count", +1 do
|
336
|
-
|
352
|
+
Group.import [group]
|
337
353
|
end
|
338
354
|
assert_equal "superx", Group.first.order
|
339
355
|
end
|
@@ -347,14 +363,14 @@ describe "#import" do
|
|
347
363
|
end
|
348
364
|
|
349
365
|
context "importing through an association scope" do
|
350
|
-
[
|
351
|
-
context "when validation is " + (
|
366
|
+
[true, false].each do |bool|
|
367
|
+
context "when validation is " + (bool ? "enabled" : "disabled") do
|
352
368
|
it "should automatically set the foreign key column" do
|
353
|
-
books = [[
|
369
|
+
books = [["David Chelimsky", "The RSpec Book"], ["Chad Fowler", "Rails Recipes"]]
|
354
370
|
topic = FactoryGirl.create :topic
|
355
|
-
topic.books.import [
|
371
|
+
topic.books.import [:author_name, :title], books, validate: bool
|
356
372
|
assert_equal 2, topic.books.count
|
357
|
-
assert topic.books.all? { |
|
373
|
+
assert topic.books.all? { |book| book.topic_id == topic.id }
|
358
374
|
end
|
359
375
|
end
|
360
376
|
end
|
@@ -362,8 +378,8 @@ describe "#import" do
|
|
362
378
|
it "works importing models" do
|
363
379
|
topic = FactoryGirl.create :topic
|
364
380
|
books = [
|
365
|
-
Book.new(:
|
366
|
-
Book.new(:
|
381
|
+
Book.new(author_name: "Author #1", title: "Book #1"),
|
382
|
+
Book.new(author_name: "Author #2", title: "Book #2"),
|
367
383
|
]
|
368
384
|
topic.books.import books
|
369
385
|
assert_equal 2, topic.books.count
|
@@ -373,24 +389,19 @@ describe "#import" do
|
|
373
389
|
|
374
390
|
it "works importing array of columns and values" do
|
375
391
|
topic = FactoryGirl.create :topic
|
376
|
-
books = [
|
377
|
-
Book.new(:author_name => "Foo", :title => "Baz"),
|
378
|
-
Book.new(:author_name => "Foo2", :title => "Baz2"),
|
379
|
-
]
|
380
392
|
topic.books.import [:author_name, :title], [["Author #1", "Book #1"], ["Author #2", "Book #2"]]
|
381
393
|
assert_equal 2, topic.books.count
|
382
394
|
assert topic.books.detect { |b| b.title == "Book #1" && b.author_name == "Author #1" }
|
383
395
|
assert topic.books.detect { |b| b.title == "Book #2" && b.author_name == "Author #2" }
|
384
396
|
end
|
385
|
-
|
386
397
|
end
|
387
398
|
|
388
399
|
context 'When importing models with Enum fields' do
|
389
400
|
it 'should be able to import enum fields' do
|
390
401
|
Book.delete_all if Book.count > 0
|
391
402
|
books = [
|
392
|
-
Book.new(:
|
393
|
-
Book.new(:
|
403
|
+
Book.new(author_name: "Foo", title: "Baz", status: 0),
|
404
|
+
Book.new(author_name: "Foo2", title: "Baz2", status: 1),
|
394
405
|
]
|
395
406
|
Book.import books
|
396
407
|
assert_equal 2, Book.count
|
@@ -404,12 +415,27 @@ describe "#import" do
|
|
404
415
|
end
|
405
416
|
end
|
406
417
|
|
418
|
+
it 'should be able to import enum fields with default value' do
|
419
|
+
Book.delete_all if Book.count > 0
|
420
|
+
books = [
|
421
|
+
Book.new(author_name: "Foo", title: "Baz")
|
422
|
+
]
|
423
|
+
Book.import books
|
424
|
+
assert_equal 1, Book.count
|
425
|
+
|
426
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
427
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
428
|
+
else
|
429
|
+
assert_equal 0, Book.first.read_attribute('status')
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
407
433
|
if ENV['AR_VERSION'].to_f > 4.1
|
408
434
|
it 'should be able to import enum fields by name' do
|
409
435
|
Book.delete_all if Book.count > 0
|
410
436
|
books = [
|
411
|
-
Book.new(:
|
412
|
-
Book.new(:
|
437
|
+
Book.new(author_name: "Foo", title: "Baz", status: :draft),
|
438
|
+
Book.new(author_name: "Foo2", title: "Baz2", status: :published),
|
413
439
|
]
|
414
440
|
Book.import books
|
415
441
|
assert_equal 2, Book.count
|
@@ -447,17 +473,42 @@ describe "#import" do
|
|
447
473
|
describe "importing serialized fields" do
|
448
474
|
it "imports values for serialized fields" do
|
449
475
|
assert_difference "Widget.unscoped.count", +1 do
|
450
|
-
Widget.import [:w_id, :data], [[1, {:
|
476
|
+
Widget.import [:w_id, :data], [[1, { a: :b }]]
|
451
477
|
end
|
452
|
-
assert_equal({:
|
478
|
+
assert_equal({ a: :b }, Widget.find_by_w_id(1).data)
|
453
479
|
end
|
454
480
|
|
455
|
-
|
481
|
+
if ENV['AR_VERSION'].to_f >= 3.1
|
482
|
+
let(:data) { { a: :b } }
|
456
483
|
it "imports values for serialized JSON fields" do
|
457
484
|
assert_difference "Widget.unscoped.count", +1 do
|
458
|
-
Widget.import [:w_id, :json_data], [[9,
|
485
|
+
Widget.import [:w_id, :json_data], [[9, data]]
|
486
|
+
end
|
487
|
+
assert_equal(data.as_json, Widget.find_by_w_id(9).json_data)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
describe "#import!" do
|
493
|
+
let(:columns) { %w(title author_name) }
|
494
|
+
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
495
|
+
let(:invalid_values) { [["Rails Recipes", "Chad Fowler"], ["The RSpec Book", ""], ["Agile+UX", ""]] }
|
496
|
+
|
497
|
+
context "with invalid data" do
|
498
|
+
it "should raise ActiveRecord::RecordInvalid" do
|
499
|
+
assert_no_difference "Topic.count" do
|
500
|
+
assert_raise ActiveRecord::RecordInvalid do
|
501
|
+
Topic.import! columns, invalid_values
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
context "with valid data" do
|
508
|
+
it "should import data" do
|
509
|
+
assert_difference "Topic.count", +2 do
|
510
|
+
Topic.import! columns, valid_values
|
459
511
|
end
|
460
|
-
assert_equal({:a => :b}.as_json, Widget.find_by_w_id(9).json_data)
|
461
512
|
end
|
462
513
|
end
|
463
514
|
end
|