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.
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