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