class Eco::Data::Locations::NodeDiff # Adjusts ArrayDiff for Nodes diffs in a location structure class NodesDiff < Eco::Data::Hashes::ArrayDiff require_relative 'nodes_diff/selectors' require_relative 'nodes_diff/diffs_tree' require_relative 'nodes_diff/clustered_treeify' extend Eco::Data::Locations::NodeDiff::NodesDiff::Selectors SELECTORS = %i[ id name id_name classifications insert unarchive update move archive ].freeze selector(*SELECTORS) class_resolver :diff_result_class, Eco::Data::Locations::NodeDiff attr_reader :original_tree def initialize(*args, original_tree:, **kargs, &block) super(*args, **kargs, &block) @original_tree = original_tree mark_implicit_unarchive! end def diffs @diffs ||= super.select do |dff| # discard entries that are to be inserted and archived at the same time next false if dff.insert? && dff.archive?(validate: false) dff.unarchive? || dff.id_name? || dff.insert? || dff.move? || dff.archive? end end def diffs_details # rubocop:disable Metrics/AbcSize section = '#' * 10 msg = '' if insert? msg << " #{section} I N S E R T S #{section}\n" msg << insert.map {|dff| dff.diff_hash.pretty_inspect}.join end if update? msg << "\n #{section} U P D A T E S #{section}\n" update.each do |dff| flags = '' flags << 'i' if dff.id? flags << 'n' if dff.name? flags << 'c' if dff.classifications? flags << 'm' if dff.move? flags << 'u' if dff.unarchive? msg << "<< #{flags} >> " msg << dff.diff_hash.pretty_inspect end end if archive? msg << "\n #{section} A R C H I V E S #{section}\n" msg << archive.map {|dff| dff.diff_hash.pretty_inspect}.join end msg end def diffs_summary # rubocop:disable Metrics/AbcSize comp = "(#{source_2.count} input nodes VS #{source_1.count} live nodes)" return "There were no differences identified #{comp}" if diffs.empty? msg = [] msg << "Identified #{diffs.count} differences #{comp}:" msg << when_present(insert) do |count| " • #{count} nodes to insert" end msg << when_present(update) do |count| " • #{count} nodes to update" end msg << when_present(unarchive) do |count| " • #{count} nodes to unarchive (includes ancestors of target nodes)" end msg << when_present(id) do |count| " • #{count} nodes to change id\n" end msg << when_present(name) do |count| " • #{count} nodes to change name" end msg << when_present(classifications) do |count| " • #{count} nodes to change classifications" end msg << when_present(move) do |count| " • #{count} nodes to move" end msg << when_present(archive) do |count| " • #{count} nodes to archive" end msg.compact.join("\n") end alias_method :unarchive_src, :unarchive # @note we must unarchive destination parents that will get # some children as well def unarchive unarchive_src | @implicit_unarchive end private def when_present(list, default = nil) raise ArgumentError, "Expecting block but not given" unless block_given? count = list.count return yield(count) if count&.positive? default end def mark_implicit_unarchive! @implicit_unarchive = source_results.select do |result| result.archived_prev && implicit_unarchive?(result) end.tap do |results| results.each(&:unarchive!) end end def implicit_unarchive?(result) [result.node_id, result.node_id_prev].compact.any? do |id| target_parent_ids.include?(id) end end def target_parent_ids return @target_parent_ids if instance_variable_defined?(:@target_parent_ids) @target_parent_ids = insert.map(&:parent_id).uniq @target_parent_ids |= move.map(&:parent_id) end end end