require 'id_hash' module IdsOfAllDependencies def self.get_model(name) self.subclasses.find{ |m| m.name == name.to_s.camelcase } end def ids_of_all_dependencies(to_filter=nil, filtering_strategy=:with_parents) ids_of_all_dependencies_with_filtered(to_filter, filtering_strategy)[:main] end def ids_of_all_dependencies_with_filtered(to_filter=nil, filtering_strategy=:with_parents) id_hash = ids_of_all_dependencies_without_reflection(to_filter || {}, filtering_strategy) move_wrongly_assigned_to_main(to_filter, id_hash) if to_filter && filtering_strategy == :with_parents id_hash[:main].sort_arrays! id_hash[:filtered_out].sort_arrays! id_hash end def ids_of_all_dependencies_without_reflection(to_filter, filtering_strategy=:with_parents) result = { main: IdHash.new, filtered_out: IdHash.new } self_symbol = self.class.name.underscore.to_sym self.class.reflect_on_all_associations.map do |association| next if association.macro == :belongs_to symbol = association.klass.name.underscore.to_sym context = { to_filter: to_filter, self_symbol: self_symbol, association: association, strategy: filtering_strategy } self.send(association.name).map do |associated_object| hash_to_use = get_hash_to_use(result, **context, object: associated_object) hash_to_use.add(symbol, associated_object.id) end result = get_result_with_grandchildren_hashes(result, context) end result end private def get_result_with_grandchildren_hashes(result, context) hashes = get_grandchildren_hashes(context) main = hashes.map { |hash| hash[:main] } filtered_out = hashes.map { |hash| hash[:filtered_out] } result[:main] =result[:main].join(*main) result[:filtered_out] = result[:filtered_out].join(*filtered_out) result end def get_grandchildren_hashes(context) association = context[:association] to_filter = context[:to_filter] self.send(association.name).map do |associated_object| next if should_be_filtered?(**context, object: associated_object) associated_object.ids_of_all_dependencies_without_reflection(to_filter, context[:strategy]) end.compact end def get_hash_to_use(result, context) symbol = should_be_filtered?(**context) ? :filtered_out : :main result[symbol] end def should_be_filtered?(context) case context[:strategy] when :with_parents should_be_filtered_according_to_with_parents_strategy?(context) when :without_parents should_be_filtered_according_to_without_parents_strategy?(context) end end def should_be_filtered_according_to_with_parents_strategy?(context) to_filter = context[:to_filter] object = context[:object] association = context[:association] symbol = association.klass.name.underscore.to_sym association.klass.reflect_on_all_associations.each do |association2| next if association2.macro == :belongs_to context = { to_filter: to_filter, symbol: symbol, association: association2 } return true if object.send(association2.name).any? && is_this_association_filtered?(**context) end false end def is_this_association_filtered?(to_filter:, symbol:, association:) arr = to_filter[symbol] arr.present? && arr.any? { |a| a == association.name } end def should_be_filtered_according_to_without_parents_strategy?(context) is_this_association_filtered?( to_filter: context[:to_filter], symbol: context[:self_symbol], association: context[:association] ) end def move_wrongly_assigned_to_main(to_filter, id_hash) id_hash[:filtered_out].each do |model_symbol, array| array.clone.each do |id| object = self.class.get_model(model_symbol).find(id) move_object_to_main_if_necessary(to_filter[model_symbol], id_hash, object) end end id_hash end def move_object_to_main_if_necessary(associations_to_filter, id_hash, object) if should_object_be_moved?(associations_to_filter, id_hash, object) symbol = object.class.name.underscore.to_sym id_hash[:filtered_out][symbol].delete(object.id) id_hash[:main].add(symbol, object.id) end end def should_object_be_moved?(associations_to_filter, id_hash, object) associations_to_filter.map do |association| associated = object.send(association) associated.to_a.empty? || associated.map do |associated_object| class_symbol = associated_object.class.name.underscore.to_sym id_hash[:main][class_symbol]&.include?(associated_object.id) end.reduce(:&) end.reduce(:&) end end