# frozen_string_literal: true class AssociationPreloadInstrumentation def instrument(_type, field) return field unless field.metadata.include?(:preload) old_resolver = field.resolve_proc new_resolver = -> (object, args, ctx) do preload(object, field.metadata[:preload]).then do old_resolver.call(object, args, ctx) end end field.redefine do resolve(new_resolver) end end private def preload(object, associations) if associations.is_a?(Symbol) preload_single_association(object, associations) else promises = [] Array.wrap(associations).each do |value| case value when Symbol promises << preload_single_association(object, value) when Array value.each { |sub_value| promises << preload(object, sub_value) } when Hash value.each do |key, sub_value| promises << preload_single_association(object, key).then do next_value = object.public_send(key) case next_value when ActiveRecord::Base preload(next_value, sub_value) else Promise.all(Array.wrap(next_value).map { |next_model| preload(next_model, sub_value) }) end end end end end Promise.all(promises) end end def preload_single_association(object, association) return Promise.resolve(object) if object.association(association).loaded? AssociationLoader.for(object.class, association).load(object) end end