class GraphQL::Preload::Instrument
Provides an instrument for the GraphQL::Field :preload definition
Public Instance Methods
instrument(_type, field)
click to toggle source
# File lib/graphql/preload/instrument.rb, line 5 def instrument(_type, field) metadata = merged_metadata(field) return field if metadata.fetch(:preload, nil).nil? old_resolver = field.resolve_proc new_resolver = ->(obj, args, ctx) do return old_resolver.call(obj, args, ctx) unless obj if metadata[:preload_scope] scope = metadata[:preload_scope].call(args, ctx) end is_graphql_object = obj.is_a?(GraphQL::Schema::Object) respond_to_object = obj.respond_to?(:object) record = is_graphql_object && respond_to_object ? obj.object : obj preload(record, metadata[:preload], scope).then do old_resolver.call(obj, args, ctx) end end field.redefine do resolve(new_resolver) end end
Private Instance Methods
merged_metadata(field)
click to toggle source
# File lib/graphql/preload/instrument.rb, line 86 def merged_metadata(field) type_class = field.metadata.fetch(:type_class, nil) if type_class.nil? || !type_class.respond_to?(:to_graphql) field.metadata else field.metadata.merge(type_class.to_graphql.metadata) end end
preload(record, associations, scope)
click to toggle source
# File lib/graphql/preload/instrument.rb, line 31 def preload(record, associations, scope) if associations.is_a?(String) raise TypeError, "Expected #{associations} to be a Symbol, not a String" elsif associations.is_a?(Symbol) return preload_single_association(record, associations, scope) end promises = [] Array.wrap(associations).each do |association| case association when Symbol promises << preload_single_association(record, association, scope) when Array association.each do |sub_association| promises << preload(record, sub_association, scope) end when Hash association.each do |sub_association, nested_association| promises << preload_single_association(record, sub_association, scope).then do associated_records = record.public_send(sub_association) case associated_records when ActiveRecord::Base preload(associated_records, nested_association, scope) else Promise.all( Array.wrap(associated_records).map do |associated_record| preload(associated_record, nested_association, scope) end ) end end end end end Promise.all(promises) end
preload_single_association(record, association, scope)
click to toggle source
# File lib/graphql/preload/instrument.rb, line 71 def preload_single_association(record, association, scope) # We would like to pass the `scope` (which is an `ActiveRecord::Relation`), # directly into `Loader.for`. However, because the scope is # created for each parent record, they are different objects and # return different loaders, breaking batching. # Therefore, we pass in `scope.to_sql`, which is the same for all the # scopes and set the `scope` using an accessor. The actual scope # object used will be the last one, which shouldn't make any difference, # because even though they are different objects, they are all # functionally equivalent. loader = GraphQL::Preload::Loader.for(record.class, association, scope.try(:to_sql)) loader.scope = scope loader.load(record) end