buren/honey_format

View on GitHub
lib/honey_format/matrix/row_builder.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module HoneyFormat
  # Holds data for a single row.
  class RowBuilder
    # Returns a new instance of RowBuilder.
    # @return [RowBuilder] a new instance of RowBuilder.
    # @param [Array<Symbol>] columns an array of symbols.
    # @param builder [#call, #to_csv] optional row builder.
    # @param type_map [Hash] map of column_name => type conversion to perform.
    # @raise [RowError] super class of errors raised when there is a row error.
    # @raise [EmptyRowColumnsError] raised when there are no columns.
    # @raise [InvalidRowLengthError] raised when row has more columns than header columns.
    # @example Create new row
    #     RowBuilder.new!([:id])
    def initialize(columns, builder: nil, type_map: {})
      if columns.empty?
        err_msg = 'Expected array with at least one element, but was empty.'
        raise(Errors::EmptyRowColumnsError, err_msg)
      end

      @type_map = type_map
      @converter = HoneyFormat.converter_registry

      @row_klass = Row.new(*columns)
      @builder = builder
      @columns = columns
    end

    # Returns an object representing the row.
    # @return [Row, Object] a new instance of built row.
    # @param row [Array] the row array.
    # @raise [InvalidRowLengthError]
    #   raised when there are more row elements longer than columns
    # @example Build new row
    #     r = RowBuilder.new([:id])
    #     r.build(['1']).id #=> '1'
    def build(row)
      build_row!(row)
    rescue ArgumentError => e
      raise unless e.message == 'struct size differs'

      raise_invalid_row_length!(e, row)
    end

    private

    # Returns Struct
    # @return [Row, Object] a new instance of built row.
    # @param row [Array] the row array.
    # @raise [ArgumentError] raised when struct fails to build
    # @example Build new row
    #     r = RowBuilder.new([:id])
    #     r.build(['1']).id #=> '1'
    def build_row!(row)
      row = @row_klass.call(row)

      # Convert values
      @type_map.each do |column, type|
        types = type.respond_to?(:each) ? type : [type]
        value = row[column]
        types.each { |t| value = @converter.call(value, t) }

        row[column] = value
      end

      return row unless @builder

      @builder.call(row)
    end

    # Raises invalid row length error
    # @param [StandardError] e the raised error
    # @param [Object] row
    # @raise [Errors::InvalidRowLengthError]
    def raise_invalid_row_length!(e, row)
      err_msg = [
        "Row length #{row.length}",
        "column length #{@columns.length}",
        "row: #{row.inspect}",
        "orignal message: '#{e.message}'",
      ].join(', ')
      raise(Errors::InvalidRowLengthError, err_msg)
    end
  end
end