class Bai2::Record

This class represents a record. It knows how to parse the single record information, but has no knowledge of the structure of the file.

Constants

AssertVersion2

This block ensures that only version 2 of the BAI standard is accepted

CleanContinuedText

Cleans up text in continuations, removing leading commas

ParseDate

Returns a date object

ParseMilitaryTime

Returns a time interval in seconds, to be added to the date

ParseTypeCode

Parses a type code, returns a structured informative hash

RECORD_CODES
SIMPLE_FIELD_MAP

For each record code, this defines a simple way to automatically parse the fields. Each field has a list of the keys. Some keys are not simply string types, in which case they will be formatted as a tuple (key, fn), where fn is a block (or anything that responds to `to_proc`) that will be called to cast the value (e.g. `:to_i`).

Attributes

code[R]
physical_record_count[R]
raw[R]

Public Class Methods

new(line, physical_record_count = 1, options: {}) click to toggle source
# File lib/bai2/record.rb, line 115
def initialize(line, physical_record_count = 1, options: {})
  @code = RECORD_CODES[line[0..1]]
  @physical_record_count = physical_record_count
  # clean / delimiter
  @raw = if options[:continuations_slash_delimit_end_of_line_only]
          # Continuation records for transaction details extend the text fields
          # and they may begin with 88,/ but should include the rest of the line.
          # A proper fix would involve each continuation record knowing what field it was extending.
          line.sub(/\/$/, '')
         else
          line.sub(/,\/.+$/, '').sub(/\/$/, '')
         end
end

Public Instance Methods

[](key) click to toggle source

A record can be accessed like a hash.

# File lib/bai2/record.rb, line 140
def [](key)
  fields[key]
end
fields() click to toggle source

NOTE: fields is called upon first use, so as not to parse records right away in case they might be merged with a continuation.

# File lib/bai2/record.rb, line 134
def fields
  @fields ||= parse_raw(@code, @raw)
end

Private Instance Methods

parse_account_identifier_fields(record) click to toggle source
# File lib/bai2/record.rb, line 194
def parse_account_identifier_fields(record)

  # split out the constant bits
  record_code, customer, currency_code, rest = record.split(',', 4).map(&:strip)

  common = {
    record_code:   record_code,
    customer:      customer,
    currency_code: currency_code,
    summaries:     [],
  }

  # sadly, imperative style seems cleaner. would prefer it functional.
  until rest.nil? || rest.empty?

    type_code, amount, items_count, funds_type, rest \
      = rest.split(',', 5).map(&:strip)

    amount_details = {
      type:          ParseTypeCode[type_code],
      amount:        amount.to_i,
      items_count:   items_count,
      funds_type:    funds_type,
    }

    # handle funds_type logic
    funds_info, rest = *parse_funds_type(funds_type, rest)
    with_funds_availability = amount_details.merge(funds_info)

    common[:summaries] << with_funds_availability
  end

  common
end
parse_funds_type(funds_type, rest) click to toggle source

Takes a `fund_type` field, and the rest, and return a hashed of interpreted values, and the new rest.

funds_type, rest = ...
funds_info, rest = *parse_funds_type(funds_type, rest)
# File lib/bai2/record.rb, line 235
def parse_funds_type(funds_type, rest)
  info = \
    case funds_type
    when 'S'
      now, next_day, later, rest = rest.split(',', 4).map(&:strip)
      {
        availability: [
          {day: 0,    amount: now},
          {day: 1,    amount: now},
          {day: '>1', amount: now},
        ]
      }
    when 'V'
      value_date, value_hour, rest = rest.split(',', 3).map(&:strip)
      value_hour = '2400' if value_hour == '9999'
      {
        value_dated: {date: value_date, hour: value_hour}
      }
    when 'D'
      field_count, rest = rest.split(',', 2).map(&:strip)
      availability = field_count.to_i.times.map do
        days, amount, rest = rest.split(',', 3).map(&:strip)
        {days: days.to_i, amount: amount}
      end
      {availability: availability}
    else
      {}
    end
  [info, rest]
end
parse_raw(code, line) click to toggle source
# File lib/bai2/record.rb, line 146
def parse_raw(code, line)

  fields = (SIMPLE_FIELD_MAP[code] || [])
  if !fields.empty?
    split = line.split(',', fields.count).map(&:strip)
    Hash[fields.zip(split).map do |k,v|
      next [k,v] if k.is_a?(Symbol)
      key, block = k
      [key, block.to_proc.call(v)]
    end]
  elsif respond_to?("parse_#{code}_fields".to_sym, true)
    send("parse_#{code}_fields".to_sym, line)
  else
    raise ParseError.new('Unknown record code.')
  end
end
parse_transaction_detail_fields(record) click to toggle source

Special cases need special implementations.

The rules here are pulled from the specification at this URL: www.bai.org/Libraries/Site-General-Downloads/Cash_Management_2005.sflb.ashx

# File lib/bai2/record.rb, line 168
def parse_transaction_detail_fields(record)

  # split out the constant bits
  record_code, type_code, amount, funds_type, rest = record.split(',', 5).map(&:strip)

  common = {
    record_code: record_code,
    type:        ParseTypeCode[type_code],
    amount:      amount.to_i,
    funds_type:  funds_type,
  }

  # handle funds_type logic
  funds_info, rest = *parse_funds_type(funds_type, rest)
  with_funds_availability = common.merge(funds_info)

  # split the rest of the constant fields
  bank_ref, customer_ref, text = rest.split(',', 3).map(&:strip)

  with_funds_availability.merge(
    bank_reference:     bank_ref,
    customer_reference: customer_ref,
    text:               CleanContinuedText[text],
  )
end