Skip to main content
edited title
Link
200_success
  • 144.2k
  • 22
  • 188
  • 473

Refactor Ruby ActiveRecord importing library

added 768 characters in body
Source Link
Hauleth
  • 143
  • 7
class Invoice < ActiveRecord::Base
  act_as_importable do |row, company|
    invoice = company.invoices.find_or_initialize_by_name(row[:name].to_s)

    vat_in = if row[:client].is_a? String
               row[:client]
             else
               row[:client].to_i
             end
    client = company.clients.find_by_vat_in(vat_in.to_s)

    invoice.client = client

    invoice.pay_date = row[:pay_date]
    invoice.full_amount = row[:full_amount].to_i
    invoice.paid_amount = row[:paid_amount].to_i

    invoice.save!

    invoice
  end
end

The importing in controller looks like:

  def save
    file = params[:file]

    logger.debug file.path

    Invoice.transaction do
      @invoices = Invoice.import_from(file, current_company)
      @invoices.each(&:save)
    end
  rescue RuntimeError => error
    logger.debug error
    redirect_to import_company_clients_path, alert: I18n.t('invalid_file')
  rescue ActiveRecord::RecordInvalid
    redirect_to import_company_clients_path, alert: I18n.t('invalid_record')
  else
    respond_to do |format|
      format.html
      format.json { render json: @invoices }
    end
  end
act_as_importable do |row, company|
  invoice = company.invoices.find_or_initialize_by_name(row[:name].to_s)

  vat_in = if row[:client].is_a? String
             row[:client]
           else
             row[:client].to_i
           end
  client = company.clients.find_by_vat_in(vat_in.to_s)

  invoice.client = client

  invoice.pay_date = row[:pay_date]
  invoice.full_amount = row[:full_amount].to_i
  invoice.paid_amount = row[:paid_amount].to_i

  invoice.save!

  invoice
end
class Invoice < ActiveRecord::Base
  act_as_importable do |row, company|
    invoice = company.invoices.find_or_initialize_by_name(row[:name].to_s)

    vat_in = if row[:client].is_a? String
               row[:client]
             else
               row[:client].to_i
             end
    client = company.clients.find_by_vat_in(vat_in.to_s)

    invoice.client = client

    invoice.pay_date = row[:pay_date]
    invoice.full_amount = row[:full_amount].to_i
    invoice.paid_amount = row[:paid_amount].to_i

    invoice.save!

    invoice
  end
end

The importing in controller looks like:

  def save
    file = params[:file]

    logger.debug file.path

    Invoice.transaction do
      @invoices = Invoice.import_from(file, current_company)
      @invoices.each(&:save)
    end
  rescue RuntimeError => error
    logger.debug error
    redirect_to import_company_clients_path, alert: I18n.t('invalid_file')
  rescue ActiveRecord::RecordInvalid
    redirect_to import_company_clients_path, alert: I18n.t('invalid_record')
  else
    respond_to do |format|
      format.html
      format.json { render json: @invoices }
    end
  end
Source Link
Hauleth
  • 143
  • 7

Refactor Ruby ActiveRecord importing library

In my Rails app I need to import some files from CSV and Excel files. I needed it in 2 models so I have written lib/importable.rb:

module Importable
  def self.included(base)
    base.send :extend, ActAsMethods
  end

  module ActAsMethods
    def act_as_importable(&block)
      @import_block = block
      send :extend, ImportableMethods
    end

    attr_reader :import_block
  end

  module ImportableMethods
    def import_from(file, *args)
      raise 'This model is not importable' if import_block.nil?

      send(:before_import, *args) if respond_to? :before_import

      sheet = open_spreadsheet(file)

      header = sheet.row(1).map(&:to_sym)

      (2..sheet.last_row).map do |i|
        row = Hash[header.zip(sheet.row(i))]

        import_block.call(row, *args)
      end
    end

    private
    def open_spreadsheet(file)
      logger.info "Importing file #{file.original_filename}"
      case File.extname(file.original_filename)
      when '.csv'  then Csv.new(file.path, nil, :ignore)
      when '.xls'  then Excel.new(file.path, nil, :ignore)
      when '.xlsx' then Excelx.new(file.path, nil, :ignore)
      when '.ods'  then Openoffice.new(file.path, nil, :ignore)
      else raise "Unknown file type: #{file.original_filename}"
      end
    rescue
      raise "Cannot open file: #{file.original_filename}"
    end
  end
end

class ActiveRecord::Base
  include Importable
end

And in model I need to write, i.e.:

act_as_importable do |row, company|
  invoice = company.invoices.find_or_initialize_by_name(row[:name].to_s)

  vat_in = if row[:client].is_a? String
             row[:client]
           else
             row[:client].to_i
           end
  client = company.clients.find_by_vat_in(vat_in.to_s)

  invoice.client = client

  invoice.pay_date = row[:pay_date]
  invoice.full_amount = row[:full_amount].to_i
  invoice.paid_amount = row[:paid_amount].to_i

  invoice.save!

  invoice
end

But IMHO it's a little overbloated. Have you any suggedtions to make it more reagable and Gemified?