Skip to content

Commit d40df13

Browse files
committed
Add batch_transaction to make transaction work on model validation*
This is the core part when importing from csv. ```ruby def batch_import @resource.transaction do run_callback(:before_batch_import) batch_result = resource.import(headers.values, csv_lines, import_options) run_callback(:after_batch_import) batch_result end end ``` What confusing me is that the transaction won't rollback, when some records fail on validation. It means that now the csv file is partially imported, and I need to modify the failed records and delete the succeed records to import again, which seems weird to me. The transaction relies on the `import` method defined by *activerecord-import*. The `import` method does the validation and saves the result to `failed_instances`, which will never raise an exception on validation. Check the code for details https://github.com/zdennis/activerecord-import/blob/6c06b3ceb53f83e1ce930eab35cdd4b6375c57ca/lib/activerecord-import/import.rb#L340-L365 So I made this update to manually trigger a rollback: ```ruby raise ActiveRecord::Rollback if import_options[:batch_transaction] && batch_result.failed_instances.any? ``` P.S. The validation I'm talking about is on model level, not on DB level. The `import` method will raise the DB level exception to tirgger the rollback, like add a record with a duiplicate field which has a uniq index.
1 parent ca7d286 commit d40df13

File tree

4 files changed

+13
-6
lines changed

4 files changed

+13
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Tool | Description
6565
:csv_options |hash with column separator, row separator, etc
6666
:validate |bool means perform validations or not
6767
:batch_size |integer value of max record count inserted by 1 query/transaction
68+
:batch_transaction |bool (false by default), if transaction is used when batch importing and works when :validate is set to true
6869
:before_import |proc for before import action, hook called with importer object
6970
:after_import |proc for after import action, hook called with importer object
7071
:before_batch_import |proc for before each batch action, called with importer object

lib/active_admin_import/dsl.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module DSL
88
# +back+:: resource action to redirect after processing
99
# +csv_options+:: hash to override default CSV options
1010
# +batch_size+:: integer value of max record count inserted by 1 query/transaction
11+
# +batch_transaction+:: bool (false by default), if transaction is used when batch importing and works when :validate is set to true
1112
# +before_import+:: proc for before import action, hook called with importer object
1213
# +after_import+:: proc for after import action, hook called with importer object
1314
# +before_batch_import+:: proc for before each batch action, called with importer object
@@ -31,11 +32,12 @@ module DSL
3132
if result.empty?
3233
flash[:warning] = I18n.t('active_admin_import.file_empty_error')
3334
else
34-
if result.has_imported?
35-
flash[:notice] = I18n.t('active_admin_import.imported', count: result.imported_qty, model: model_name, plural_model: plural_model_name)
36-
end
3735
if result.has_failed?
3836
flash[:error] = I18n.t('active_admin_import.failed', count: result.failed.count, model: model_name, plural_model: plural_model_name)
37+
return if options[:batch_transaction]
38+
end
39+
if result.has_imported?
40+
flash[:notice] = I18n.t('active_admin_import.imported', count: result.imported_qty, model: model_name, plural_model: plural_model_name)
3941
end
4042
end
4143
end

lib/active_admin_import/importer.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Importer
1616
:after_batch_import,
1717
:headers_rewrites,
1818
:batch_size,
19+
:batch_transaction,
1920
:csv_options
2021
].freeze
2122

@@ -47,7 +48,7 @@ def import
4748
end
4849

4950
def import_options
50-
@import_options ||= options.slice(:validate, :on_duplicate_key_update, :ignore, :timestamps)
51+
@import_options ||= options.slice(:validate, :on_duplicate_key_update, :ignore, :timestamps, :batch_transaction)
5152
end
5253

5354
def batch_replace(header_key, options)
@@ -97,12 +98,14 @@ def run_callback(name)
9798
end
9899

99100
def batch_import
101+
batch_result = nil
100102
@resource.transaction do
101103
run_callback(:before_batch_import)
102104
batch_result = resource.import(headers.values, csv_lines, import_options)
105+
raise ActiveRecord::Rollback if import_options[:batch_transaction] && batch_result.failed_instances.any?
103106
run_callback(:after_batch_import)
104-
batch_result
105107
end
108+
batch_result
106109
end
107110

108111
def assign_options(options)

lib/active_admin_import/options.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module Options
66
:csv_options,
77
:validate,
88
:batch_size,
9+
:batch_transaction,
910
:before_import,
1011
:after_import,
1112
:before_batch_import,
@@ -40,4 +41,4 @@ def self.options_for(config, options= {})
4041
end
4142

4243
end
43-
end
44+
end

0 commit comments

Comments
 (0)