From b7eb2160658229b87bef38f8f1d1bc2ba1549ff4 Mon Sep 17 00:00:00 2001 From: Alexis Reigel <mail@koffeinfrei.org> Date: Fri, 1 Jun 2018 10:34:02 +0200 Subject: [PATCH] add transaction support --- lib/excelsior/import.rb | 42 ++++++++++++------- lib/excelsior/transaction.rb | 15 +++++++ test/excelsior_test.rb | 78 ++++++++++++++++++++++++++++++++---- 3 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 lib/excelsior/transaction.rb diff --git a/lib/excelsior/import.rb b/lib/excelsior/import.rb index 98087a5..d4e0527 100644 --- a/lib/excelsior/import.rb +++ b/lib/excelsior/import.rb @@ -5,11 +5,13 @@ require 'excelsior/source' require 'excelsior/mapping' require 'excelsior/error' require 'excelsior/report' +require 'excelsior/transaction' module Excelsior class Import include Source include Mapping + include Transaction attr_accessor :source, :fields, :errors, :report attr_accessor :rows, :columns @@ -29,21 +31,15 @@ module Excelsior valid? end - def run # takes an optional block - @rows.map.with_index do |row, i| - attributes = map_row_values(row, @columns) - if block_given? - begin - result = yield attributes - report_insert - result - rescue - report_failure - end - else - record = model_class.create(attributes) - add_model_errors(record, i) + def run(&block) + if self.class.use_transaction + model_class.transaction do + insert_rows(&block) + + raise ActiveRecord::Rollback if @report.failed > 0 end + else + insert_rows(&block) end end @@ -84,5 +80,23 @@ module Excelsior def report_failure @report.failed += 1 end + + def insert_rows(&block) + @rows.map.with_index do |row, i| + attributes = map_row_values(row, @columns) + if block_given? + begin + result = block.call(attributes) + report_insert + result + rescue + report_failure + end + else + record = model_class.create(attributes) + add_model_errors(record, i) + end + end + end end end diff --git a/lib/excelsior/transaction.rb b/lib/excelsior/transaction.rb new file mode 100644 index 0000000..d3a2247 --- /dev/null +++ b/lib/excelsior/transaction.rb @@ -0,0 +1,15 @@ +module Excelsior + module Transaction + def self.included(host_class) + host_class.extend ClassMethods + end + + module ClassMethods + attr_accessor :use_transaction + + def transaction(use_transaction) + self.use_transaction = use_transaction + end + end + end +end diff --git a/test/excelsior_test.rb b/test/excelsior_test.rb index 946db56..259dc2d 100644 --- a/test/excelsior_test.rb +++ b/test/excelsior_test.rb @@ -1,20 +1,22 @@ require "test_helper" require "excelsior/import" -class UserImport < Excelsior::Import - source "test/files/complete.xlsx" - - map "Vorname", to: :first_name - map "Nachname", to: :last_name - map "E-Mail", to: :email -end - class User < ActiveRecord::Base validates :first_name, presence: true end describe Excelsior do before do + Object.send(:remove_const, :UserImport) if Object.constants.include?(:UserImport) + + class UserImport < Excelsior::Import + source "test/files/complete.xlsx" + + map "Vorname", to: :first_name + map "Nachname", to: :last_name + map "E-Mail", to: :email + end + @import = UserImport.new end @@ -34,6 +36,66 @@ describe Excelsior do end end + describe 'transaction' do + describe 'transaction is not set' do + it 'inserts only the valid rows' do + UserImport.new("test/files/missing-first-name.xlsx").run + assert_equal 2, User.count + end + end + + describe 'transaction is set to true' do + before do + Object.send(:remove_const, :UserImport) if Object.constants.include?(:UserImport) + + class UserImport < Excelsior::Import + source "test/files/complete.xlsx" + transaction true + + map "Vorname", to: :first_name + map "Nachname", to: :last_name + map "E-Mail", to: :email + end + end + + describe 'without a block' do + let(:import) { UserImport.new("test/files/missing-first-name.xlsx") } + + before do + import.run + end + + it 'rolls back if one record fails' do + assert_equal 0, User.count + end + + it 'returns the report' do + assert_equal 2, import.report.inserted + assert_equal 1, import.report.failed + assert_equal 3, import.report.total + end + end + + describe 'with a block' do + it 'rolls back if one record raises an error' do + UserImport.new("test/files/missing-first-name.xlsx").run do |v| + User.create!(v) + end + + assert_equal 0, User.count + end + + it 'does not roll back if the block does not raise an error' do + UserImport.new("test/files/missing-first-name.xlsx").run do |v| + User.create(v) + end + + assert_equal 2, User.count + end + end + end + end + describe '#rows' do it 'returns the correct number of rows' do assert_equal 2, @import.rows.length -- GitLab