class Processor::Base

Base class for a Processor, all processors must inherit from it rubocop:disable Metrics/ClassLength

Attributes

headers[RW]
loader_options[R]
statement_from[RW]
statement_to[RW]

Public Class Methods

new(opts) click to toggle source

@option opts [String] :file Path to the CSV file to process

# File lib/ynab_convert/processor/base.rb, line 17
def initialize(opts)
  logger.debug "Initializing processor with options: `#{opts.to_h}'"
  raise ::Errno::ENOENT unless File.exist? opts[:file]

  @file = opts[:file]
  @headers = { transaction_date: nil, payee: nil, debit: nil, credit: nil }
end

Public Instance Methods

to_ynab!() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 25
def to_ynab!
  begin
    convert!
    rename_file
  rescue YnabConvert::Error
    invalid_csv_file
  end
ensure
  logger.debug "Deleting temp file `#{temp_filename}'"
  delete_temp_csv
end

Protected Instance Methods

convert!() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 96
def convert!
  logger.debug "Will write to `#{temp_filename}'"

  CSV.open(temp_filename, 'wb', output_options) do |converted|
    CSV.foreach(@file, 'rb', loader_options) do |row|
      logger.debug "Parsing row: `#{row.to_h}'"
      # Some rows don't contain valid or useful data
      catch :skip_row do
        extract_header_names(row)
        ynab_row = transformers(row)
        if inflow_or_outflow_missing?(ynab_row) ||
           transaction_date_missing?(ynab_row)
          logger.debug 'Empty row, skipping it'
          skip_row(row)
        end
        converted << ynab_row
        record_statement_interval_dates(ynab_row)
      end

      logger.debug 'Done converting'
    end
  end
end
date_is_further_away?(date) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 92
def date_is_further_away?(date)
  statement_from.nil? || statement_from > date
end
date_is_more_recent?(date) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 88
def date_is_more_recent?(date)
  statement_to.nil? || statement_to < date
end
delete_temp_csv() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 57
def delete_temp_csv
  FileUtils.remove_file temp_filename, force: true
end
extract_transaction_date(ynab_row) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 65
def extract_transaction_date(ynab_row)
  transaction_date_index = 0
  ynab_row[transaction_date_index]
end
file_uid() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 131
def file_uid
  @file_uid ||= rand(36**8).to_s(36)
end
inflow_or_outflow_missing?(row) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 41
def inflow_or_outflow_missing?(row)
  inflow_index = 3
  outflow_index = 4
  # If there is neither inflow and outflow values, or their value is 0,
  # then the row is not valid to YNAB4
  (row[inflow_index].nil? || row[inflow_index].empty? ||
   row[inflow_index] == '0.00') &&
    (row[outflow_index].nil? || row[outflow_index].empty? ||
     row[outflow_index] == '0.00')
end
invalid_csv_file() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 126
def invalid_csv_file
  raise YnabConvert::Error, "Unable to parse file `#{@file}'. Is it a "\
    "valid CSV file from #{@institution_name}?"
end
output_filename() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 140
def output_filename
  # If the file contained no parsable CSV data, from and to dates will be
  # nil.
  # This is to avoid a NoMethodError on NilClass.
  raise YnabConvert::Error if statement_from.nil? || statement_to.nil?

  from = statement_from.strftime('%Y%m%d')
  to = statement_to.strftime('%Y%m%d')

  "#{File.basename(@file, '.csv')}_#{@institution_name.snake_case}_"\
    "#{from}-#{to}_ynab4.csv"
end
output_options() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 157
def output_options
  {
    converters: %i[numeric date],
    force_quotes: true,
    write_headers: true,
    headers: ynab_headers
  }
end
record_statement_interval_dates(ynab_row) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 70
def record_statement_interval_dates(ynab_row)
  transaction_date_index = 0
  date = Date.parse(ynab_row[transaction_date_index])

  if date_is_further_away?(date)
    logger.debug "Replacing statement_from `#{statement_from.inspect}' "\
      "with `#{date}'"
    self.statement_from = date
  end
  # rubocop:disable Style/GuardClause
  if date_is_more_recent?(date)
    logger.debug "Replacing statement_to `#{statement_to.inspect}' with "\
      "`#{date}'"
    self.statement_to = date
  end
  # rubocop:enable Style/GuardClause
end
rename_file() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 120
def rename_file
  File.rename(temp_filename, output_filename)
  logger.debug "Renamed temp file `#{temp_filename}' to "\
    "`#{output_filename}'"
end
skip_row(row) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 52
def skip_row(row)
  logger.debug "Found empty row, skipping it: #{row.to_h}"
  throw :skip_row
end
temp_filename() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 135
def temp_filename
  "#{File.basename(@file, '.csv')}_#{@institution_name.snake_case}_"\
    "#{file_uid}_ynab4.csv"
end
transaction_date_missing?(ynab_row) click to toggle source
# File lib/ynab_convert/processor/base.rb, line 61
def transaction_date_missing?(ynab_row)
  ynab_row[0].nil? || [0].empty?
end
transformers() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 166
def transformers
  raise NotImplementedError, :transformers
end
ynab_headers() click to toggle source
# File lib/ynab_convert/processor/base.rb, line 153
def ynab_headers
  %w[Date Payee Memo Outflow Inflow]
end