Skip to content

Commit

Permalink
Merge pull request #266 from jkowens/model_validation
Browse files Browse the repository at this point in the history
Improve performance of importing models with validations
  • Loading branch information
zdennis authored Jun 22, 2016
2 parents db02550 + d0322ab commit 8f9c3fd
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 21 deletions.
50 changes: 29 additions & 21 deletions lib/activerecord-import/import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,7 @@ def import_helper( *args )
# this next line breaks sqlite.so with a segmentation fault
# if model.new_record? || options[:on_duplicate_key_update]
column_names.map do |name|
name = name.to_s
if respond_to?(:defined_enums) && defined_enums.key?(name) # ActiveRecord 5
model.read_attribute(name)
else
model.read_attribute_before_type_cast(name)
end
model.read_attribute_before_type_cast(name.to_s)
end
# end
end
Expand Down Expand Up @@ -394,7 +389,19 @@ def import_helper( *args )
end

return_obj = if is_validating
import_with_validations( column_names, array_of_attributes, options )
if models
import_with_validations( column_names, array_of_attributes, options ) do |failed|
models.each_with_index do |model, i|
model = model.dup if options[:recursive]
next if model.valid?(options[:validate_with_context])
model.send(:raise_record_invalid) if options[:raise_error]
array_of_attributes[i] = nil
failed << model
end
end
else
import_with_validations( column_names, array_of_attributes, options )
end
else
(num_inserts, ids) = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
ActiveRecord::Import::Result.new([], num_inserts, ids)
Expand Down Expand Up @@ -431,21 +438,24 @@ def import_from_table( options ) # :nodoc:
def import_with_validations( column_names, array_of_attributes, options = {} )
failed_instances = []

# create instances for each of our column/value sets
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
if block_given?
yield failed_instances
else
# create instances for each of our column/value sets
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )

# keep track of the instance and the position it is currently at. if this fails
# validation we'll use the index to remove it from the array_of_attributes
arr.each_with_index do |hsh, i|
instance = new do |model|
# keep track of the instance and the position it is currently at. if this fails
# validation we'll use the index to remove it from the array_of_attributes
model = new
arr.each_with_index do |hsh, i|
hsh.each_pair { |k, v| model[k] = v }
next if model.valid?(options[:validate_with_context])
raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
array_of_attributes[i] = nil
failed_instances << model.dup
end

next if instance.valid?(options[:validate_with_context])
raise(ActiveRecord::RecordInvalid, instance) if options[:raise_error]
array_of_attributes[i] = nil
failed_instances << instance
end

array_of_attributes.compact!

num_inserts, ids = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
Expand Down Expand Up @@ -643,9 +653,7 @@ def add_special_rails_stamps( column_names, array_of_attributes, options )

# Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
def validations_array_for_column_names_and_attributes( column_names, array_of_attributes ) # :nodoc:
array_of_attributes.map do |attributes|
Hash[attributes.each_with_index.map { |attr, c| [column_names[c], attr] }]
end
array_of_attributes.map { |values| Hash[column_names.zip(values)] }
end
end
end
19 changes: 19 additions & 0 deletions test/import_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,25 @@
end
end

context 'When importing arrays of values with Enum fields' do
let(:columns) { [:author_name, :title, :status] }
let(:values) { [['Author #1', 'Book #1', 0], ['Author #2', 'Book #2', 1]] }

it 'should be able to import enum fields' do
Book.delete_all if Book.count > 0
Book.import columns, values
assert_equal 2, Book.count

if ENV['AR_VERSION'].to_i >= 5.0
assert_equal 'draft', Book.first.read_attribute('status')
assert_equal 'published', Book.last.read_attribute('status')
else
assert_equal 0, Book.first.read_attribute('status')
assert_equal 1, Book.last.read_attribute('status')
end
end
end

describe "importing when model has default_scope" do
it "doesn't import the default scope values" do
assert_difference "Widget.unscoped.count", +2 do
Expand Down

0 comments on commit 8f9c3fd

Please sign in to comment.