activerecord-import 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +49 -0
  3. data/.rubocop_todo.yml +36 -0
  4. data/.travis.yml +31 -7
  5. data/CHANGELOG.md +19 -0
  6. data/Gemfile +5 -2
  7. data/README.markdown +6 -1
  8. data/Rakefile +5 -2
  9. data/activerecord-import.gemspec +1 -1
  10. data/benchmarks/benchmark.rb +67 -68
  11. data/benchmarks/lib/base.rb +136 -137
  12. data/benchmarks/lib/cli_parser.rb +106 -107
  13. data/benchmarks/lib/mysql2_benchmark.rb +19 -21
  14. data/benchmarks/lib/output_to_csv.rb +2 -1
  15. data/benchmarks/lib/output_to_html.rb +8 -13
  16. data/benchmarks/schema/mysql_schema.rb +8 -8
  17. data/gemfiles/4.0.gemfile +1 -1
  18. data/gemfiles/4.1.gemfile +1 -1
  19. data/gemfiles/4.2.gemfile +1 -1
  20. data/gemfiles/5.0.gemfile +1 -1
  21. data/lib/activerecord-import.rb +2 -0
  22. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +0 -1
  23. data/lib/activerecord-import/adapters/abstract_adapter.rb +9 -9
  24. data/lib/activerecord-import/adapters/mysql_adapter.rb +17 -17
  25. data/lib/activerecord-import/adapters/postgresql_adapter.rb +20 -22
  26. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +9 -9
  27. data/lib/activerecord-import/base.rb +3 -3
  28. data/lib/activerecord-import/import.rb +152 -131
  29. data/lib/activerecord-import/synchronize.rb +20 -20
  30. data/lib/activerecord-import/value_sets_parser.rb +7 -6
  31. data/lib/activerecord-import/version.rb +1 -1
  32. data/test/adapters/mysql2spatial.rb +1 -1
  33. data/test/adapters/postgis.rb +1 -1
  34. data/test/adapters/postgresql.rb +1 -1
  35. data/test/adapters/spatialite.rb +1 -1
  36. data/test/adapters/sqlite3.rb +1 -1
  37. data/test/import_test.rb +121 -70
  38. data/test/models/book.rb +5 -6
  39. data/test/models/chapter.rb +2 -2
  40. data/test/models/discount.rb +3 -0
  41. data/test/models/end_note.rb +2 -2
  42. data/test/models/promotion.rb +1 -1
  43. data/test/models/question.rb +1 -1
  44. data/test/models/rule.rb +2 -2
  45. data/test/models/topic.rb +3 -3
  46. data/test/models/widget.rb +1 -1
  47. data/test/postgis/import_test.rb +1 -1
  48. data/test/schema/generic_schema.rb +100 -96
  49. data/test/schema/mysql_schema.rb +5 -7
  50. data/test/sqlite3/import_test.rb +0 -2
  51. data/test/support/active_support/test_case_extensions.rb +12 -15
  52. data/test/support/assertions.rb +1 -1
  53. data/test/support/factories.rb +15 -16
  54. data/test/support/generate.rb +4 -4
  55. data/test/support/mysql/import_examples.rb +21 -21
  56. data/test/support/postgresql/import_examples.rb +83 -55
  57. data/test/support/shared_examples/on_duplicate_key_update.rb +23 -23
  58. data/test/synchronize_test.rb +2 -2
  59. data/test/test_helper.rb +6 -8
  60. data/test/value_sets_bytes_parser_test.rb +14 -17
  61. data/test/value_sets_records_parser_test.rb +6 -6
  62. metadata +7 -4
  63. 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=[self.primary_key])
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(&"#{key}".to_sym) }
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
- if matched_instance
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
- if instance.respond_to?(:clear_changes_information)
48
- instance.clear_changes_information # Rails 4.1 and higher
49
- else
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
- # Since the instance now accurately reflects the record in
54
- # the database, ensure that instance.persisted? is true.
55
- instance.instance_variable_set '@new_record', false
56
- instance.instance_variable_set '@destroyed', false
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, current_arr_values_size, current_size = [], 0, 0
18
- values.each_with_index do |val,i|
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 = [ val ]
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
- [ *value_sets ]
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, with_fill=false)
52
+ @values.in_groups_of(max_records, false)
52
53
  end
53
54
  end
54
55
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.12.0"
3
+ VERSION = "0.13.0".freeze
4
4
  end
5
5
  end
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "mysql2spatial"
1
+ ENV["ARE_DB"] = "mysql2spatial"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "postgis"
1
+ ENV["ARE_DB"] = "postgis"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "postgresql"
1
+ ENV["ARE_DB"] = "postgresql"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "spatialite"
1
+ ENV["ARE_DB"] = "spatialite"
@@ -1 +1 @@
1
- ENV["ARE_DB"] = "sqlite3"
1
+ ENV["ARE_DB"] = "sqlite3"
@@ -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, [["foo", "bar"]]
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 = [["foo", "bar"]].freeze
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) { [[ "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", ""]] }
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
- result = Topic.import columns, valid_values, :validate => false
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
- result = Topic.import columns, invalid_values, :validate => false
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']], :validate => false
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
- result = Topic.import columns, valid_values, :validate => true
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
- result = Topic.import columns, valid_values_with_context, :validate_with_context => :context_test
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
- result = Topic.import columns, invalid_values, :validate => true
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
- result = Topic.import columns, valid_values, :validate_with_context => :context_test
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, :validate => true
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
- result = Topic.import columns, valid_values + invalid_values, :validate => true
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) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
117
- let(:invalid_values) { [[ "The RSpec Book", ""], ["Agile+UX", ""]] }
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
- result = Topic.import columns, valid_values, :all_or_none => true
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
- result = Topic.import columns, invalid_values, :all_or_none => true
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
- result = Topic.import columns, mixed_values, :all_or_none => true
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, :all_or_none => true
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, :all_or_none => true
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, :synchronize => 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, :synchronize => new_topics, :synchronize_keys => [:title] )
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 &:destroy
180
- Topic.import(new_topics, :synchronize => new_topics, :synchronize_keys => [:title] )
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, :title => "The RSpec Book", :author_name => "David Chelimsky")}
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
- result = Topic.import topics
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, :title => "foobar")
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
- result = Topic.import topics, :validate => true
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
- result = Topic.import invalid_topics, :validate => true
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
- result = Topic.import invalid_topics, :validate => false
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
- result = Topic.import [:author_name, :title], topics
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
- result = Topic.import [:author_name, :title], topics
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 Chronic.parse("5 minutes ago") do
292
+ Timecop.freeze(time) do
275
293
  assert_difference "Book.count", +2 do
276
- result = Book.import ["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]]
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" do
283
- assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @new_book.created_at.strftime("%H:%M")
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
- assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @new_book.created_on.strftime("%H:%M")
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" do
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
- assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @new_book.updated_at.strftime("%H:%M")
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
- assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @new_book.updated_on.strftime("%H:%M")
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
- result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
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" do
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, :order => "superx") }
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
- result = Group.import [group]
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
- [ true, false ].each do |b|
351
- context "when validation is " + (b ? "enabled" : "disabled") do
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 = [[ "David Chelimsky", "The RSpec Book" ], [ "Chad Fowler", "Rails Recipes" ]]
369
+ books = [["David Chelimsky", "The RSpec Book"], ["Chad Fowler", "Rails Recipes"]]
354
370
  topic = FactoryGirl.create :topic
355
- topic.books.import [ :author_name, :title ], books, :validate => b
371
+ topic.books.import [:author_name, :title], books, validate: bool
356
372
  assert_equal 2, topic.books.count
357
- assert topic.books.all? { |b| b.topic_id == topic.id }
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(:author_name => "Author #1", :title => "Book #1"),
366
- Book.new(:author_name => "Author #2", :title => "Book #2"),
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(:author_name => "Foo", :title => "Baz", status: 0),
393
- Book.new(:author_name => "Foo2", :title => "Baz2", status: 1),
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(:author_name => "Foo", :title => "Baz", status: :draft),
412
- Book.new(:author_name => "Foo2", :title => "Baz2", status: :published),
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, {:a => :b}]]
476
+ Widget.import [:w_id, :data], [[1, { a: :b }]]
451
477
  end
452
- assert_equal({:a => :b}, Widget.find_by_w_id(1).data)
478
+ assert_equal({ a: :b }, Widget.find_by_w_id(1).data)
453
479
  end
454
480
 
455
- requires_active_record_version ">= 4" do
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, {:a => :b}]]
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