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 :source1, :source2 attr_reader :src_h1, :src_h2 def initialize(source1, source2, logger: nil) @logger = logger if logger @source1 = source1 @source2 = source2 @src_h1 = by_key(source1) @src_h2 = by_key(source2) raise "Missing source1" unless !!self.src_h1 raise "Missing source2" unless !!self.src_h2 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 do |res| res.diff? end end # @return [Boolean] wheter or not there are differences. def diffs? !diffs.empty? end # All the items that contain the diff of a node. # @return [Array] def source_results @source_results ||= paired_sources.each_with_object([]) do |(src1, src2), res| res << diff_result_class.new(src1, src2) end end protected # It pairs the hashes of `source1` and `source2` # @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 keys1 = src_h1.keys keys2 = src_h2.keys all_keys = keys1 | keys2 all_keys.map {|key| [src_h1[key], src_h2[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 def compared_attrs diff_result_class.compared_attrs.map(&:to_s) end private def symbolize_keys(hash) hash.each_with_object({}) do |(k, v), h| h[k.to_sym] = v end end def stringify_keys(hash) hash.each_with_object({}) do |(k, v), h| h[k.to_s] = v end 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 logger.error("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) { "Input content 'Array' of '#{sample.class}' is not supported." } exit(1) end else log(:error) { "Could not obtain any data out content: '#{content.class}'" } exit(1) end end end end end end