FinalCAD/csv_row_model

View on GitHub
lib/csv_row_model/concerns/import/base.rb

Summary

Maintainability
A
35 mins
Test Coverage
require 'csv_row_model/concerns/inspect'

module CsvRowModel
  module Import
    module Base
      extend ActiveSupport::Concern
      include Inspect
      INSPECT_METHODS = %i[source_attributes initialized_at parent context previous].freeze

      included do
        attr_reader :source_headers, :source_row, :line_number, :index, :previous
        validate { errors.add(:csv, "has #{@csv_exception.message}") if @csv_exception }
      end

      # @param [Array] source_row_or_exception the csv row
      # @param options [Hash]
      # @option options [Integer] :index 1st row_model is 0, 2nd is 1, 3rd is 2, etc.
      # @option options [Integer] :line_number line_number in the CSV file
      # @option options [Array] :source_headers the csv header row
      # @option options [CsvRowModel::Import] :previous the previous row model
      # @option options [CsvRowModel::Import] :parent if the instance is a child, pass the parent
      def initialize(source_row_or_exception=[], options={})
        @source_row = source_row_or_exception
        @csv_exception = source_row if source_row.kind_of? Exception
        @source_row = [] if source_row_or_exception.class != Array

        @line_number, @index, @source_headers = options[:line_number], options[:index], options[:source_headers]

        @previous = options[:previous].try(:dup)
        previous.try(:free_previous)
        super(options)
      end

      # Free `previous` from memory to avoid making a linked list
      def free_previous
        attributes
        @previous = nil
      end

      # Safe to override.
      #
      # @return [Boolean] returns true, if this instance should be skipped
      def skip?
        !valid?
      end

      # Safe to override.
      #
      # @return [Boolean] returns true, if the entire csv file should stop reading
      def abort?
        false
      end

      class_methods do
        #
        # Move to Import::File once FileModel is removed.
        #
        # @param [Import::File] file to read from
        # @param [Hash] context extra data you want to work with the model
        # @param [Import] prevuous the previous row model
        # @return [Import] the next model instance from the csv
        def next(file, context={})
          csv = file.csv
          csv.skip_headers
          row_model = nil

          loop do # loop until the next parent or end_of_file? (need to read children rows)
            csv.read_row
            row_model ||= new(csv.current_row,
                              line_number: csv.line_number,
                              index: file.index,
                              source_headers: csv.headers,
                              context: context,
                              previous: file.previous_row_model)

            return row_model if csv.end_of_file?

            next_row_is_parent = !row_model.append_child(csv.next_row)
            return row_model if next_row_is_parent
          end
        end
      end
    end
  end
end