# frozen_string_literal: true module Torque module PostgreSQL module Associations module Preloader module Association delegate :connected_through_array?, to: :@reflection # For reflections connected through an array, make sure to properly # decuple the list of ids and set them as associated with the owner def run return self if run? return super unless connected_through_array? @run = true send("run_array_for_#{@reflection.macro}") self end # Correctly correlate records when they are connected theough an array def set_inverse(record) return super unless connected_through_array? && @reflection.macro == :has_many # Only the first owner is associated following the same instruction # on the original implementation convert_key(record[association_key_name])&.each do |key| if owners = owners_by_key[key] association = owners.first.association(reflection.name) association.set_inverse_instance(record) end end end # Requires a slight change when running on has many since the value # of the foreign key being an array def load_records(raw_records = nil) return super unless connected_through_array? && @reflection.macro == :has_many @records_by_owner = {}.compare_by_identity raw_records ||= loader_query.records_for([self]) @preloaded_records = raw_records.select do |record| assignments = false keys = convert_key(record[association_key_name]) || [] owners_by_key.values_at(*keys).each do |owner| entries = (@records_by_owner[owner] ||= []) if reflection.collection? || entries.empty? entries << record assignments = true end end assignments end end # Make sure to change the process when connected through an array def owners_by_key return super unless connected_through_array? @owners_by_key ||= owners.each_with_object({}) do |owner, result| Array.wrap(convert_key(owner[owner_key_name])).each do |key| (result[key] ||= []) << owner end end end private # Specific run for belongs_many association def run_array_for_belongs_to_many # Add reverse to has_many records = groupped_records owners.each do |owner| items = records.values_at(*Array.wrap(owner[owner_key_name])) associate_records_to_owner(owner, items.flatten) end end # Specific run for has_many association def run_array_for_has_many # Add reverse to belongs_to_many records = Hash.new { |h, k| h[k] = [] } groupped_records.each do |ids, record| ids.each { |id| records[id].concat(Array.wrap(record)) } end records.default_proc = nil owners.each do |owner| associate_records_to_owner(owner, records[owner[owner_key_name]] || []) end end # Build correctly the constraint condition in order to get the # associated ids def records_for(ids, &block) return super unless connected_through_array? condition = scope.arel_table[association_key_name] condition = reflection.build_id_constraint(condition, ids.flatten.uniq) scope.where(condition).load(&block) end def associate_records_to_owner(owner, records) return super unless connected_through_array? association = owner.association(reflection.name) association.loaded! association.target.concat(records) end def groupped_records preloaded_records.group_by do |record| convert_key(record[association_key_name]) end end end ::ActiveRecord::Associations::Preloader::Association.prepend(Association) end end end end