module Eco module Data module Hashes class ArrayDiff extend Eco::Language::Models::ClassHelpers # We can change the `DiffResult` class (items) class_resolver :diff_result_class, "Eco::Data::Hash::DiffResult" include Eco::Language::AuxiliarLogger attr_reader :source_1, :source_2 attr_reader :src_h_1, :src_h_2 def initialize(source_1, source_2, logger: nil) @logger = logger if logger @source_1 = source_1 @source_2 = source_2 raise "Missing source_1" unless (@src_h_1 = by_key(source_1)) raise "Missing source_2" unless (@src_h_2 = by_key(source_2)) end # @note # - A `Hash::DiffResult` object, offers `hash_diff` with the attrs that have changed value # - It also allows to know the original value # @return [Hash] where `key` is the key of the record, and `value` a `DiffResult` object def diffs @diffs ||= source_results.select(&:diff?) end # @return [Boolean] wheter or not there are differences. def diffs? diffs.any? end # All the items that contain the diff of a node. # @return [Array] def source_results @source_results ||= paired_sources.each_with_object([]) do |(src_1, src_2), res| res << diff_result_class.new(src_1, src_2) end end protected # It pairs the hashes of `source_1` and `source_2` # @note # - It also ensures they are in their Hash form (with string keys) # - This will merge entries of the same source that hold the same `key` attr value (latest wins) def paired_sources all_keys = src_h_1.keys | src_h_2.keys all_keys.map {|key| [src_h_1[key], src_h_2[key]]} end # @return [String] the `key` attribute of `diff_result_class` def key diff_result_class.key.tap do |k| raise "#{diff_result_class}: missing main key attr to pair records. Given: #{k}" unless k.is_a?(String) end end def case_sensitive? diff_result_class.case_sensitive? end private def symbolize_keys(hash) hash.transform_keys(&:to_sym) end def stringify_keys(hash) hash.transform_keys(&:to_s) end def by_key(content) to_array_of_hashes(content).each_with_object({}) do |item, out| out[item[key]] = item end end def to_array_of_hashes(content) case content when Hash log(:error) { "(ArrayDiff) Input data as 'Hash' not supported. Expecting 'Enumerable' or 'String'" } exit(1) when String to_array_of_hashes(Eco::CSV.parse(content)) when Enumerable sample = content.to_a.first case sample when Hash, Array, ::CSV::Row Eco::CSV::Table.new(content).to_array_of_hashes else log(:error) { "(ArrayDiff) Input content 'Array' of '#{sample.class}' is not supported." } exit(1) end else log(:error) { "(ArrayDiff) Could not obtain any data out content: '#{content.class}'" } exit(1) end end end end end end