class CSVDecision::Result

Accumulate the matching row(s) into a result hash. @api private

Attributes

attributes[R]

@return [Hash{Symbol=>Object}, Hash{Integer=>Object}] The decision result hash containing

both result values and if: columns, which eventually get evaluated and removed.
multi_result[R]

@return [Boolean] Returns true if this is a multi-row result

outs[R]

@return [Hash{Index=>Dictionary::Entry}] Output columns.

outs_functions[R]

@return [nil, true] Set to true if the table has output functions.

Public Class Methods

new(table:) click to toggle source

(see Decision.initialize)

# File lib/csv_decision/result.rb, line 25
def initialize(table:)
  @outs = table.columns.outs
  @outs_functions = table.outs_functions
  @table = table
end

Public Instance Methods

accumulate_outs(row) click to toggle source

Accumulate the outs into arrays of values. @param row [Array] @return [void]

# File lib/csv_decision/result.rb, line 58
def accumulate_outs(row)
  @outs.each_pair { |col, column| add_cell(column_name: column.name, cell: row[col]) }
end
add_outs(row) click to toggle source

Common case for building a single row result is just copying output column values to the final result hash. @param row [Array] @return [void]

# File lib/csv_decision/result.rb, line 51
def add_outs(row)
  @outs.each_pair { |col, column| @attributes[column.name] = row[col] }
end
eval_cell_proc(proc:, column_name:, index:) click to toggle source

Evaluate the cell proc using the partial result calculated so far.

@param proc [Matchers::Pro] @param column_name [Symbol, Integer] @param index [Integer]

# File lib/csv_decision/result.rb, line 91
def eval_cell_proc(proc:, column_name:, index:)
  @attributes[column_name][index] = proc.function[partial_result(index)]
end
eval_outs(row) click to toggle source

Evaluate the output columns, and use them to start building the final result, along with the partial result required to evaluate functions.

@param row [Array] @return (see final)

# File lib/csv_decision/result.rb, line 76
def eval_outs(row)
  # Set the constants first, in case the functions refer to them
  eval_outs_constants(row: row)

  # Then evaluate the procs, left to right
  eval_outs_procs(row: row)

  final_result
end
final_result() click to toggle source

Derive the final result. @return [Hash{Symbol=>Object}]

# File lib/csv_decision/result.rb, line 64
def final_result
  # If there are no if: columns, then nothing needs to be filtered out of this result hash.
  return @attributes if @table.columns.ifs.empty?

  @multi_result ? multi_row_result : single_row_result
end
input(data) click to toggle source

Initialize the object for new input data.

@param data [Hash{Symbol=>Object}] Input data hash. @return [void]

# File lib/csv_decision/result.rb, line 35
def input(data)
  # Attributes hash contains the output decision key value pairs
  @attributes = {}
  @multi_result = false
  # Partial result always copies in the input hash for calculating output functions.
  # Note that these input key values will not be mutated, as output columns can never
  # have the same symbol as an input hash key.
  # However, the rest of this hash is mutated as output column evaluation results
  # are accumulated.
  @partial_result = data.slice(*@table.columns.input_keys) if @outs_functions
end

Private Instance Methods

add_cell(column_name:, cell:) click to toggle source
# File lib/csv_decision/result.rb, line 186
def add_cell(column_name:, cell:)
  case (current = @attributes[column_name])
  when nil
    @attributes[column_name] = cell

  when Matchers::Proc
    @attributes[column_name] = [current, cell]
    @multi_result = true

  when Array
    @attributes[column_name] << cell

  else
    @attributes[column_name] = [current, cell]
    @multi_result = true
  end
end
check_if_column(col) click to toggle source
# File lib/csv_decision/result.rb, line 116
def check_if_column(col)
  delete_rows = []
  @attributes[col].each_with_index { |value, index| delete_rows << index unless value }

  # Remove this if: column from the final result
  @attributes.delete(col)

  # Adjust the row index as we delete rows in sequence.
  delete_rows.each_with_index { |index, sequence| delete_row(index - sequence) }
end
delete_row(index) click to toggle source

Each result “row”, given by the row index is a collection of column arrays. @param index [Integer] Row index. @return [{Symbol=>Object}, {Integer=>Object}]

# File lib/csv_decision/result.rb, line 130
def delete_row(index)
  @attributes.transform_values { |value| value.delete_at(index) }
end
eval_out_proc(cell:, column_name:, column_type:) click to toggle source
# File lib/csv_decision/result.rb, line 165
def eval_out_proc(cell:, column_name:, column_type:)
  @attributes[column_name] = cell.function[@partial_result]

  # Do not add if: columns to the partial result
  return if column_type == :if
  @partial_result[column_name] = @attributes[column_name]
end
eval_outs_constants(row:) click to toggle source
# File lib/csv_decision/result.rb, line 146
def eval_outs_constants(row:)
  @outs.each_pair do |col, column|
    cell = row[col]
    next if cell.is_a?(Matchers::Proc)

    @partial_result[column.name] = cell
    @attributes[column.name] = cell
  end
end
eval_outs_procs(row:) click to toggle source
# File lib/csv_decision/result.rb, line 156
def eval_outs_procs(row:)
  @outs.each_pair do |col, column|
    cell = row[col]
    next unless cell.is_a?(Matchers::Proc)

    eval_out_proc(cell: cell, column_name: column.name, column_type: column.type)
  end
end
multi_row_result() click to toggle source
# File lib/csv_decision/result.rb, line 110
def multi_row_result
  @table.columns.ifs.each_key { |col| check_if_column(col) }

  normalize_result
end
normalize_result() click to toggle source

@return [{Symbol=>Object}] Decision result hash with any if: columns removed.

# File lib/csv_decision/result.rb, line 135
def normalize_result
  # Peek at the first column's result and see how many rows it contains.
  count = @attributes.values.first.count
  @multi_result = count > 1

  return {} if count.zero?
  return @attributes.transform_values!(&:first) if count == 1

  @attributes
end
partial_result(index) click to toggle source
# File lib/csv_decision/result.rb, line 173
def partial_result(index)
  @attributes.each_pair do |column_name, values|
    value = values[index]
    # Delete this column from the partial result in case there is data from a prior result row
    next @partial_result.delete(column_name) if value.is_a?(Matchers::Proc)

    # Add this constant value to the partial result row built so far.
    @partial_result[column_name] = value
  end

  @partial_result
end
single_row_result() click to toggle source

Case where we have a single row result, which either gets returned or filtered by the if: column conditions.

# File lib/csv_decision/result.rb, line 99
def single_row_result
  # All if: columns must evaluate to true
  if @table.columns.ifs.keys.all? { |col| @attributes[col] }
    # Delete if: columns from final result
    @table.columns.ifs.each_key { |col| @attributes.delete(col) }
    return @attributes
  end

  false
end