How to refactor this importer to handle validation errors? This is my importer to database from excel file. I would like to handle situations when any error raise. To not breaks whole import when one errors occurs.

For example when there is duplicated record and I have uniqueness validations. I would like to store this row id in errors table and inform about that at the end.

class DDImporter
  attr_accessor :file

  def initialize(path)
    @path = path 
  end

  def open
    @file = Roo::Excelx.new(@path)
  end

  def sheets
    @file.sheets
  end

  def extract_sheets
    sheets.each do |sheet|
      unless sheet == ("risks" || "allergies_symptoms")
        extract sheet
      end
    end
  end

  def extract sheet_name
    @file.default_sheet = sheet_name

    header = file.row 1
    2.upto(file.last_row) do |i|
      row = Hash[[header, file.row(i)].transpose]
      # row.delete "id"
      object = sheet_name.classify.constantize
      object.create!(row)
    end
  end
end
It can be as simple as :

  def initialize(path)
    @path = path 
    @imported = []
    @errors = []
  end

  def extract sheet_name
    @file.default_sheet = sheet_name

    header = file.row 1
    2.upto(file.last_row) do |i|
      row = Hash[[header, file.row(i)].transpose]
      # row.delete "id"
      model = sheet_name.classify.constantize

      # this is the part that gets the job done.
      object = model.new(row)
      if object.save
        @imported &lt;&lt; object
      else
        @rejected &lt;&lt; object
      end

    end
  end


... this way you can iterate through @imported or @rejected at the end of the import process (to display errors, or perform additional tasks).

This code can also easily be adapted to only catch exceptions : 

  def extract sheet_name
    @file.default_sheet = sheet_name

    header = file.row 1
    2.upto(file.last_row) do |i|
      row = Hash[[header, file.row(i)].transpose]
      # row.delete "id"
      object = sheet_name.classify.constantize
      object.create!(row)
    end
  rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid =&gt; error
    # rejected record for these errors can be accessed with :
    error.record
    # to resume processing, just :
    error.continue
  end