lib/para/importer/base.rb in para-0.5.4 vs lib/para/importer/base.rb in para-0.6.2

- old
+ new

@@ -1,23 +1,100 @@ module Para module Importer - class Base - attr_reader :sheet + class Base < ActiveJob::Base + include ActiveJob::Status + # Used to store import errors on the object + include ActiveModel::Validations + # Used to translate importer name with rails default `activemodel` i18n keys + extend ActiveModel::Naming - def initialize(file) - @sheet = Roo::Spreadsheet.open(file.path) - end + rescue_from Exception, with: :rescue_exception - def run + class_attribute :allows_import_errors + + attr_reader :file, :sheet + + def perform(file, options = {}) + @file = file + @sheet = Roo::Spreadsheet.open(file.attachment_path, options) + progress.total = sheet.last_row - 1 + ActiveRecord::Base.transaction do (2..(sheet.last_row)).each do |index| - import_from_row(sheet.row(index)) + begin + progress.increment + import_from_row(sheet.row(index)) + rescue ActiveRecord::RecordInvalid => error + if allows_import_errors? + add_errors_from(index, error.record) + else + raise + end + end end end + + status.update(errors: errors.full_messages) end + private + def import_from_row(row) raise '#import_from_row(row) must be defined' + end + + def add_errors_from(index, record) + # The file's row number starts at 1 and headers are striped, so we + # add 2 to the index to obtain the row number + row_name = I18n.t('para.import.row_error_prefix', number: index) + + record.errors.messages.each do |attribute, messages| + messages.each do |message| + full_message = record.errors.full_message(attribute, message) + + if (value = record.send(attribute)).presence + full_message << ' (<b>' << value << '</b>)' + end + + errors.add(row_name, full_message) + end + end + end + + def allows_import_errors? + !!self.class.allows_import_errors + end + + def self.allow_import_errors! + self.allows_import_errors = true + end + + def headers + @headers ||= sheet.row(1) + end + + def rescue_exception(exception) + status.update(status: :failed) + + tag_logger(self.class.name, self.job_id) do + ActiveSupport::Notifications.instrument "failed.active_job", + adapter: self.class.queue_adapter, job: self, exception: exception + end + + if defined?(ExceptionNotifier) + ExceptionNotifier.notify_exception( + exception, data: { + importer: self.class.name, + payload: { + file_type: file.class, + file_id: file.id, + file_name: file.attachment_file_name + } + } + ) + end + + raise exception end end end end