class ActiveImporter::Base

Constants

EVENTS

Callbacks

Attributes

header[R]

Implementation

model[R]

Implementation

params[R]
row[R]

Implementation

row_count[R]
row_errors[R]
row_index[R]

Public Class Methods

column(title, field = nil, options = nil, &block) click to toggle source
# File lib/active_importer/base.rb, line 56
def self.column(title, field = nil, options = nil, &block)
  title = title.strip
  if columns[title]
    raise "Duplicate importer column '#{title}'"
  end

  if field.is_a?(Hash)
    raise "Invalid column '#{title}': expected a single set of options" unless options.nil?
    options = field
    field = nil
  else
    options ||= {}
  end

  if field.nil? && block_given?
    raise "Invalid column '#{title}': must have a corresponding attribute, or it shouldn't have a block"
  end

  columns[title] = {
    field_name: field,
    transform: block,
    optional: !!options[:optional],
  }
end
columns() click to toggle source
# File lib/active_importer/base.rb, line 24
def self.columns
  @columns ||= {}
end
event_handlers() click to toggle source
# File lib/active_importer/base.rb, line 130
def self.event_handlers
  @event_handlers ||= EVENTS.inject({}) { |hash, event| hash.merge({event => []}) }
end
fetch_model(&block) click to toggle source
# File lib/active_importer/base.rb, line 40
def self.fetch_model(&block)
  @fetch_model_block = block
end
import(file, options = {}) click to toggle source
# File lib/active_importer/base.rb, line 81
def self.import(file, options = {})
  new(file, options).import
end
imports(klass) click to toggle source
# File lib/active_importer/base.rb, line 20
def self.imports(klass)
  @model_class = klass
end
model_class() click to toggle source
# File lib/active_importer/base.rb, line 28
def self.model_class
  @model_class
end
new(file, options = {}) click to toggle source
# File lib/active_importer/base.rb, line 168
def initialize(file, options = {})
  @row_errors = []
  @params = options.delete(:params)
  @transactional = options.fetch(:transactional, self.class.transactional?)

  raise "Importer is declared transactional at the class level" if !@transactional && self.class.transactional?

  @book = Roo::Spreadsheet.open(file, options)
  load_sheet
  load_header

  @data_row_indices = ((@header_index+1)..@book.last_row)
  @row_count = @data_row_indices.count
rescue => e
  @book = @header = nil
  @row_count = 0
  @row_index = 1
  fire_event :import_failed, e
  raise
end
on(event, &block) click to toggle source
# File lib/active_importer/base.rb, line 134
def self.on(event, &block)
  raise "Unknown ActiveImporter event '#{event}'" unless EVENTS.include?(event)
  event_handlers[event] << block
end
sheet(index) click to toggle source
# File lib/active_importer/base.rb, line 36
def self.sheet(index)
  @sheet_index = index
end
skip_rows_block() click to toggle source
# File lib/active_importer/base.rb, line 52
def self.skip_rows_block
  @skip_rows_block
end
skip_rows_if(&block) click to toggle source
# File lib/active_importer/base.rb, line 48
def self.skip_rows_if(&block)
  @skip_rows_block = block
end
transactional(flag = true) click to toggle source

Transactions

# File lib/active_importer/base.rb, line 89
def self.transactional(flag = true)
  if flag
    raise "Model class does not support transactions" unless @model_class.respond_to?(:transaction)
  end
  @transactional = !!flag
end
transactional?() click to toggle source
# File lib/active_importer/base.rb, line 96
def self.transactional?
  @transactional || false
end

Private Class Methods

fetch_model_block() click to toggle source
# File lib/active_importer/base.rb, line 44
def self.fetch_model_block
  @fetch_model_block
end
fire_event(instance, event, param = nil) click to toggle source
# File lib/active_importer/base.rb, line 146
def self.fire_event(instance, event, param = nil)
  event_handlers[event].each do |block|
    instance.instance_exec(param, &block)
  end
end
skip_row_blocks() click to toggle source
# File lib/active_importer/base.rb, line 246
def self.skip_row_blocks
  @skip_row_blocks ||= begin
                         klass = self
                         result = []
                         while klass < ActiveImporter::Base
                           block = klass.skip_rows_block
                           result << block if block
                           klass = klass.superclass
                         end
                         result
                       end
end

Public Instance Methods

abort!(message) click to toggle source
# File lib/active_importer/base.rb, line 12
def abort!(message)
  @abort_message = message
end
aborted?() click to toggle source
# File lib/active_importer/base.rb, line 16
def aborted?
  !!@abort_message
end
fetch_model() click to toggle source
# File lib/active_importer/base.rb, line 193
def fetch_model
  if fetch_model_block
    self.instance_exec(&fetch_model_block)
  else
    model_class.new
  end
end
fetch_model_block() click to toggle source
# File lib/active_importer/base.rb, line 189
def fetch_model_block
  self.class.send(:fetch_model_block)
end
import() click to toggle source
# File lib/active_importer/base.rb, line 201
def import
  transaction do
    return if @book.nil?
    fire_event :import_started
    @data_row_indices.each do |index|
      @row_index = index
      @row = row_to_hash @book.row(index)
      if skip_row?
        fire_event :row_skipped
        next
      end
      import_row
      if aborted?
        fire_event :import_aborted, @abort_message
        break
      end
    end
  end
rescue => e
  fire_event :import_aborted, e.message
  raise
ensure
  fire_event :import_finished
end
model_class() click to toggle source
# File lib/active_importer/base.rb, line 32
def model_class
  self.class.model_class
end
row_error_count() click to toggle source
# File lib/active_importer/base.rb, line 236
def row_error_count
  row_errors.count
end
row_processed_count() click to toggle source
# File lib/active_importer/base.rb, line 226
def row_processed_count
  row_index - @header_index
rescue
  0
end
row_success_count() click to toggle source
# File lib/active_importer/base.rb, line 232
def row_success_count
  row_processed_count - row_errors.count
end
transactional?() click to toggle source
# File lib/active_importer/base.rb, line 100
def transactional?
  @transactional || self.class.transactional?
end

Private Instance Methods

build_model() click to toggle source
# File lib/active_importer/base.rb, line 310
def build_model
  row.each_pair do |key, value|
    column_def = columns[key]
    next if column_def.nil? || column_def[:field_name].nil?
    field_name = column_def[:field_name]
    transform = column_def[:transform]
    value = self.instance_exec(value, &transform) if transform
    model.send("#{field_name}=", value)
  end
  fire_event :row_processing
end
columns() click to toggle source
# File lib/active_importer/base.rb, line 242
def columns
  self.class.columns
end
find_header_index() click to toggle source
# File lib/active_importer/base.rb, line 271
def find_header_index
  required_column_keys = columns.keys.reject { |title| columns[title][:optional] }
  (1..@book.last_row).each do |index|
    row = @book.row(index).map { |cell| cell.to_s.strip }
    return index if required_column_keys.all? { |item| row.include?(item) }
  end
  return nil
end
fire_event(event, param = nil) click to toggle source
# File lib/active_importer/base.rb, line 139
def fire_event(event, param = nil)
  self.class.send(:fire_event, self, event, param)
  unless self.class == ActiveImporter::Base
    self.class.superclass.send(:fire_event, self, event, param)
  end
end
import_row() click to toggle source
# File lib/active_importer/base.rb, line 289
def import_row
  begin
    @model = fetch_model
    build_model
    save_model unless aborted?
  rescue => e
    @row_errors << { row_index: row_index, error_message: e.message }
    fire_event :row_error, e
    raise if transactional?
    return false
  end
  fire_event :row_success
  true
ensure
  fire_event :row_processed
end
load_header() click to toggle source
# File lib/active_importer/base.rb, line 280
def load_header
  @header_index = find_header_index
  if @header_index
    @header = @book.row(@header_index).map(&:to_s).map(&:strip)
  else
    raise 'Spreadsheet does not contain all the expected columns'
  end
end
load_sheet() click to toggle source
# File lib/active_importer/base.rb, line 263
def load_sheet
  sheet_index = self.class.instance_variable_get(:@sheet_index)
  if sheet_index
    sheet_index = @book.sheets[sheet_index-1] if sheet_index.is_a?(Fixnum)
    @book.default_sheet = sheet_index.to_s
  end
end
row_to_hash(row) click to toggle source
# File lib/active_importer/base.rb, line 322
def row_to_hash(row)
  hash = {}
  row.each_with_index do |value, index|
    hash[@header[index]] = value
  end
  hash
end
save_model() click to toggle source
# File lib/active_importer/base.rb, line 306
def save_model
  model.save! if model.new_record? || model.changed?
end
skip_row?() click to toggle source
# File lib/active_importer/base.rb, line 259
def skip_row?
  self.class.skip_row_blocks.any? { |block| self.instance_exec(&block) }
end
transaction() { || ... } click to toggle source
# File lib/active_importer/base.rb, line 104
def transaction
  if transactional?
    model_class.transaction { yield }
  else
    yield
  end
end