This repository has been archived on 2024-06-24. You can view files and clone it, but cannot push or open issues or pull requests.
ace-of-base/lib/ace_of_base/record.rb
2019-07-10 21:03:07 +12:00

85 lines
2.6 KiB
Ruby

# frozen_string_literal: true
require 'date'
module AceOfBase
# A single row of data, including casting and validations.
class Record
include Comparable
STRING_TOO_LONG_MESSAGE = 'Field `%s` is over the maximum length of %d characters (%d in total)'
VALUE_OUTSIDE_RANGE = 'Field `%s`: %p is outside the range of %p'
INVALID_DATE_MESSAGE = 'Field `%s`: unable to parse date with format %p'
INCORRECT_COLUMN_NUMBER_MESSAGE = 'Row contains an incorrect number of columns. Expected 7, received %d'
ATTRIBUTES = %i[project shot version status finish_date internal_bid created_date].freeze
attr_reader :errors, *ATTRIBUTES
def initialize(row)
@errors = []
cast_and_validate(row)
end
def valid?
errors.empty?
end
def <=>(other)
ATTRIBUTES
.lazy
.map { |attr| public_send(attr) <=> other.public_send(attr) }
.find(-> { 0 }, &:nonzero?)
end
private
def cast_and_validate(row) # rubocop:disable Metrics/AbcSize
return add_incorrect_column_number_error(row.size) unless row.size == 7
@project = cast_string_with_limit(:project, row[0], 64)
@shot = cast_string_with_limit(:shot, row[1], 64)
@version = cast_integer_within_range(:version, row[2], 0..0xffff)
@status = cast_string_with_limit(:status, row[3], 32)
@finish_date = cast_date_with_format(:finish_date, row[4], '%Y-%m-%d')
@internal_bid = cast_float_within_range(:internal_bid, row[5], 0..0xffff)
@created_date = cast_date_with_format(:created_date, row[6], '%Y-%m-%d %H:%M')
end
def add_incorrect_column_number_error(column_count)
add_error(format(INCORRECT_COLUMN_NUMBER_MESSAGE, column_count))
end
def add_error(message)
@errors << message
nil
end
def cast_string_with_limit(field, incoming, limit)
casted = incoming.to_s
length = casted.length
return add_error(format(STRING_TOO_LONG_MESSAGE, field, limit, length)) if length > limit
casted
end
def cast_integer_within_range(field, incoming, range)
casted = incoming.to_i
return add_error(format(VALUE_OUTSIDE_RANGE, field, incoming, range)) unless range.cover?(casted)
casted
end
def cast_date_with_format(field, incoming, format)
DateTime.strptime(incoming.to_s, format)
rescue ArgumentError
add_error(format(INVALID_DATE_MESSAGE, field, format))
end
def cast_float_within_range(field, incoming, range)
casted = incoming.to_f
return add_error(format(VALUE_OUTSIDE_RANGE, field, incoming, range)) unless range.cover?(casted)
casted
end
end
end