require 'id_hash' module DependencyTree class Tree < Hash def initialize(hash={}) hash.each do |key, value| self[key] = value end end def ids_tree self_cloned = self.deep_tree_clone self_cloned.delete_key_recursive(:instance) end def status_tree self_cloned = self.deep_tree_clone self_cloned.do_recursive do |tree| begin tree[:instance].reload tree[:status] = 'present' rescue ActiveRecord::RecordNotFound => e tree[:status] = 'removed' end tree.delete(:instance) end end def status_tree_condensed result = status_tree.do_recursive do |tree| tree.each do |name, array| next unless array.class == Array new_array = array.map do |subtree| next subtree.root_duplicate_summary if subtree[:duplicate] next subtree if subtree.class != Tree || subtree.size > 2 subtree.root_summary end array.clear array.concat(new_array) end end result.do_recursive do |tree| tree[:_] = tree.root_summary tree.delete(:id) tree.delete(:status) tree.delete(:class) tree.last_to_beginning! end end def last_to_beginning! arr = self.to_a arr.unshift(arr.pop) self.clear self.merge!(arr.to_h) end def root_summary "#{self[:class]}, id #{self[:id]}, #{self[:status]}" end def root_duplicate_summary root_summary + ", duplicate" end def deep_tree_clone hash = self.clone.map do |key, array| next [key, array] unless array.class == Array new_array = array.map do |subtree| if subtree.class == Tree subtree.deep_tree_clone else subtree end end [key, new_array] end Tree.new(hash) end def delete_key_recursive(key) call_method_recursive(:delete, key) end def call_method_recursive(method, *args, &block) do_recursive do |tree| tree.send(method, *args, &block) end end def do_recursive(&block) block.call(self) self.each do |key, array| next unless array.is_a? Array array.each do |subtree| subtree.do_recursive(&block) if subtree.is_a? Tree end end self end end def dependency_tree(depth = Float::INFINITY, hash_for_duplication_check = IdHash.new) is_duplicate = hash_for_duplication_check[self.class]&.include?(id) hash_for_duplication_check.add(self.class, id) shoud_go_deeper = depth > 0 && !is_duplicate result = shoud_go_deeper ? get_associations_for_tree(depth, hash_for_duplication_check) : Tree.new result[:id] = id result[:instance] = self result[:class] = self.class.to_s.underscore result[:duplicate] = true if is_duplicate result end private def get_associations_for_tree(depth, hash_for_duplication_check) result = Tree.new self.class.reflect_on_all_associations.map do |association| next if association.macro == :belongs_to symbol = association.name self.send(association.name).sort_by(&:id).map do |associated_object| result[symbol] = [] if result[symbol].nil? result[symbol] << associated_object.dependency_tree(depth - 1, hash_for_duplication_check) end end result end end