|
| 1 | +require 'graphql/preload/field_preloader' |
| 2 | + |
1 | 3 | module GraphQL |
2 | 4 | module Preload |
3 | | - # Provides an instrument for the GraphQL::Field :preload definition |
4 | 5 | class Instrument |
5 | | - def instrument(_type, field) |
6 | | - metadata = merged_metadata(field) |
7 | | - return field if metadata.fetch(:preload, nil).nil? |
| 6 | + include FieldPreloader |
8 | 7 |
|
9 | | - old_resolver = field.resolve_proc |
10 | | - new_resolver = ->(obj, args, ctx) do |
11 | | - return old_resolver.call(obj, args, ctx) unless obj |
12 | | - |
13 | | - if metadata[:preload_scope] |
14 | | - scope = metadata[:preload_scope].call(args, ctx) |
15 | | - end |
16 | | - |
17 | | - is_graphql_object = obj.is_a?(GraphQL::Schema::Object) |
18 | | - respond_to_object = obj.respond_to?(:object) |
19 | | - record = is_graphql_object && respond_to_object ? obj.object : obj |
20 | | - |
21 | | - preload(record, metadata[:preload], scope).then do |
22 | | - old_resolver.call(obj, args, ctx) |
23 | | - end |
24 | | - end |
25 | | - |
26 | | - field.redefine do |
27 | | - resolve(new_resolver) |
28 | | - end |
29 | | - end |
30 | | - |
31 | | - private def preload(record, associations, scope) |
32 | | - if associations.is_a?(String) |
33 | | - raise TypeError, "Expected #{associations} to be a Symbol, not a String" |
34 | | - elsif associations.is_a?(Symbol) |
35 | | - return preload_single_association(record, associations, scope) |
36 | | - end |
| 8 | + def instrument(_type, field) |
| 9 | + return field unless field.metadata.include?(:preload) |
37 | 10 |
|
38 | | - promises = [] |
| 11 | + if defined?(FieldExtension) && (type_class = field.metadata[:type_class]) |
| 12 | + type_class.extension(FieldExtension) |
| 13 | + field |
| 14 | + else |
| 15 | + old_resolver = field.resolve_proc |
| 16 | + new_resolver = lambda do |obj, args, ctx| |
| 17 | + return old_resolver.call(obj, args, ctx) unless obj |
39 | 18 |
|
40 | | - Array.wrap(associations).each do |association| |
41 | | - case association |
42 | | - when Symbol |
43 | | - promises << preload_single_association(record, association, scope) |
44 | | - when Array |
45 | | - association.each do |sub_association| |
46 | | - promises << preload(record, sub_association, scope) |
47 | | - end |
48 | | - when Hash |
49 | | - association.each do |sub_association, nested_association| |
50 | | - promises << preload_single_association(record, sub_association, scope).then do |
51 | | - associated_records = record.public_send(sub_association) |
| 19 | + scope = field.metadata[:preload_scope].call(args, ctx) if field.metadata[:preload_scope] |
52 | 20 |
|
53 | | - case associated_records |
54 | | - when ActiveRecord::Base |
55 | | - preload(associated_records, nested_association, scope) |
56 | | - else |
57 | | - Promise.all( |
58 | | - Array.wrap(associated_records).map do |associated_record| |
59 | | - preload(associated_record, nested_association, scope) |
60 | | - end |
61 | | - ) |
62 | | - end |
63 | | - end |
| 21 | + preload(obj.object, field.metadata[:preload], scope).then do |
| 22 | + old_resolver.call(obj, args, ctx) |
64 | 23 | end |
65 | 24 | end |
66 | | - end |
67 | | - |
68 | | - Promise.all(promises) |
69 | | - end |
70 | 25 |
|
71 | | - private def preload_single_association(record, association, scope) |
72 | | - # We would like to pass the `scope` (which is an `ActiveRecord::Relation`), |
73 | | - # directly into `Loader.for`. However, because the scope is |
74 | | - # created for each parent record, they are different objects and |
75 | | - # return different loaders, breaking batching. |
76 | | - # Therefore, we pass in `scope.to_sql`, which is the same for all the |
77 | | - # scopes and set the `scope` using an accessor. The actual scope |
78 | | - # object used will be the last one, which shouldn't make any difference, |
79 | | - # because even though they are different objects, they are all |
80 | | - # functionally equivalent. |
81 | | - loader = GraphQL::Preload::Loader.for(record.class, association, scope.try(:to_sql)) |
82 | | - loader.scope = scope |
83 | | - loader.load(record) |
84 | | - end |
85 | | - |
86 | | - private def merged_metadata(field) |
87 | | - type_class = field.metadata.fetch(:type_class, nil) |
88 | | - |
89 | | - if type_class.nil? || !type_class.respond_to?(:to_graphql) |
90 | | - field.metadata |
91 | | - else |
92 | | - field.metadata.merge(type_class.to_graphql.metadata) |
| 26 | + field.redefine do |
| 27 | + resolve(new_resolver) |
| 28 | + end |
93 | 29 | end |
94 | 30 | end |
95 | | - |
96 | 31 | end |
97 | 32 | end |
98 | 33 | end |
0 commit comments