require_relative 'diff_result/meta' module Eco module Data module Hashes class DiffResult include Eco::Data::Hashes::DiffResult::Meta attr_reader :src_1, :src_2 def initialize(src_1, src_2) @src_1 = src_1 @src_2 = src_2 end # @return [Value] the current value of `attr` (in `src_2`) def attr(attr) get_attr(src_2, attr) end # @return [Value] the previous value of `attr` (in `src_1`) def attr_prev(attr) get_attr(src_1, attr) end # Deduplication to prevent mutability when manipulation is required. def dup(src_1: nil, src_2: nil) src_1 ||= self.src_1 src_2 ||= self.src_2 self.class.new(src_1.dup, src_2.dup) end def new? src_2 && !src_1 end def del? src_1 && !src_2 end def update? !new? && !del? && diff? end # Uniq access point to identify attrs that have changed. # @note when is `new?` or to be deleted (`del?`), there's nothing to compare. # @return [Array] hash with the list of attrs that are different # between `src_1` and `src_2`. def diff_attrs return (@diff_attrs = []) if new? || del? @diff_attrs ||= all_compared_attrs.each_with_object([]) do |kattr, out| next unless comparable_attr?(kattr) out << kattr unless eq?(attr_prev(kattr), attr(kattr)) end end # @note `diff_attrs` may not include the `key` attribute # This is always included via `new?` (new key value) and `del?` (missing key value) # @return [Boolean] was there any change? def diff? new? || del? || diff_attrs.any? end # Is the `key` attr value updated? # @note that `new?` and `del?` won't be considered as key's change def key? !(new? || del?) && diff_attr?(key) end # Is `attr` part of the attributes that change? def diff_attr?(attr) return true if new? return true if del? diff_attrs.include?(attr.to_s) end # @note the `key` attribute will always be added (even if there's no change) # @return [Hash] hash with the differences as per `src_2` def diff_hash target_attrs = [key] | all_compared_attrs return slice_attrs(src_2, *target_attrs) if new? return slice_attrs(src_1, key) if del? slice_attrs(src_2, key, *diff_attrs) end # Uniq access point to scope if an `attr` is in the scope of the diff compare. # Set of attributes that are general target to identify differences # between both sources. # @note When the class `all_compared_attrs` has not been deefined, # it uses `all_source_keys` # @return [Array] the set of attributes that are comparable in this class. def all_compared_attrs super().map(&:to_s).uniq.tap do |attrs| return all_source_keys unless attrs.any? end end # All the keys that the data comes with def all_source_keys (src_1&.keys || []) & (src_2&.keys || []) end private # Refinement over `all_compared_attrs`, provided that those that must # be comparable only **when present** in both, are filtered out when # that is not the case. def comparable_attr?(attr) attr = attr.to_s return false unless all_compared_attrs.include?(attr) return true unless self.class.compared_attr_when_present?(attr) key_present_in_both?(attr) end # Is the key `attr` present in both sources? def key_present_in_both?(attr) key_present?(src_1, attr) && key_present?(src_2, attr) end end end end end