Skip to content

Commit e4f73f0

Browse files
committed
Hack and hack until the nested load test passes
1 parent 65d9368 commit e4f73f0

File tree

4 files changed

+102
-13
lines changed

4 files changed

+102
-13
lines changed

lib/graphql/dataloader.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def self.use(schema)
88
dataloader_class: self,
99
)
1010
schema.instrument(:multiplex, instrumenter)
11+
schema.lazy_resolve(Dataloader::Loader::PendingLoad, :sync)
1112
# TODO clean this up when we can assume it's a class-based schema
1213
if !schema.is_a?(Class)
1314
schema = schema.target.class

lib/graphql/dataloader/loader.rb

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,89 @@
33
module GraphQL
44
class Dataloader
55
class Loader
6+
# TODO: this is basically a reimplementation of Promise.rb
7+
# Should I just take on that dependency, or is there a value in a
8+
# custom implementation?
9+
class PendingLoad
10+
attr_writer :loaded
11+
attr_reader :pending_thens
12+
13+
def initialize(loader, key)
14+
@loader = loader
15+
@key = key
16+
@loaded = false
17+
@pending_thens = []
18+
end
19+
20+
def sync
21+
if !@loaded
22+
@loaded = true
23+
if @loader.nil?
24+
binding.pry
25+
end
26+
@loader.sync
27+
end
28+
@loader.fulfilled_value_for(@key)
29+
end
30+
31+
def value
32+
if !@fully_loaded
33+
@fully_loaded = true
34+
v = sync
35+
if v.is_a?(PendingLoad)
36+
v = v.value
37+
end
38+
@fully_loaded_value = v
39+
end
40+
@fully_loaded_value
41+
end
42+
43+
def then(&next_block)
44+
pending_then = ThenBlock.new(self, next_block)
45+
if !@loaded
46+
@pending_thens << pending_then
47+
end
48+
pending_then
49+
end
50+
end
51+
52+
class ThenBlock < PendingLoad
53+
def initialize(pending_load, then_block)
54+
@pending_load = pending_load
55+
@then_block = then_block
56+
@loaded = false
57+
@pending_thens = []
58+
@value = nil
59+
end
60+
61+
def sync
62+
if !@loaded
63+
@loaded = true
64+
@value = @then_block.call(@pending_load.sync)
65+
@pending_thens.each(&:sync)
66+
end
67+
@value
68+
end
69+
end
70+
71+
class AllPendingLoads < PendingLoad
72+
def initialize(pending_loads)
73+
@loaded = false
74+
@value = nil
75+
@pending_loads = pending_loads
76+
@pending_thens = []
77+
end
78+
79+
def sync
80+
if !@loaded
81+
@loaded = true
82+
@value = @pending_loads.map(&:sync)
83+
@pending_thens.each(&:sync)
84+
end
85+
@value
86+
end
87+
end
88+
689
def self.load(context, *key, value)
790
self.for(context, *key).load(value)
891
end
@@ -13,7 +96,8 @@ def self.for(context, *key_parts)
1396
end
1497

1598
def self.load_all(context, key, values)
16-
GraphQL::Execution::Lazy.all(values.map { |value| load(context, key, value) })
99+
pending_loads = values.map { |value| load(context, key, value) }
100+
AllPendingLoads.new(pending_loads)
17101
end
18102

19103
def initialize(context, *key)
@@ -23,14 +107,8 @@ def initialize(context, *key)
23107
@loaded_values = {}
24108
end
25109

26-
def load(value)
27-
@promises[value] ||= GraphQL::Execution::Lazy.new do
28-
if !@loaded_values.key?(value)
29-
sync
30-
end
31-
# TODO raise if key is missing?
32-
@loaded_values[value]
33-
end
110+
def load(key)
111+
@promises[key] ||= PendingLoad.new(self, key)
34112
end
35113

36114
def sync
@@ -42,12 +120,20 @@ def sync
42120

43121
def fulfill(key, value)
44122
@loaded_values[key] = value
123+
@promises[key].loaded = true
124+
@promises[key].pending_thens.each(&:sync)
125+
value
45126
end
46127

47128
def fulfilled?(key)
48129
@loaded_values.key?(key)
49130
end
50131

132+
def fulfilled_value_for(key)
133+
# TODO raise if not loaded?
134+
@loaded_values[key]
135+
end
136+
51137
def perform(values)
52138
raise NotImplementedError, "`#{self.class}#perform` should load `values` for `@key` and return an item for each of `values`"
53139
end

spec/graphql/dataloader/batch_compat_spec.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,10 @@ def images
186186
variant_image_queries = variants.map do |variant|
187187
AssociationLoader.for(context, ProductVariant, :images).load(variant)
188188
end
189-
GraphQL::Execution::Lazy.all(variant_image_queries).then(&:flatten)
189+
# TODO this is not good -- needs a good public API
190+
GraphQL::Dataloader::Loader::AllPendingLoads.new(variant_image_queries).then(&:flatten)
190191
end
191-
GraphQL::Execution::Lazy.all([product_image_query, variant_images_query]).then do
192+
GraphQL::Dataloader::Loader::AllPendingLoads.new([product_image_query, variant_images_query]).then do
192193
[product_image_query.value] + variant_images_query.value
193194
end
194195
end

spec/graphql/dataloader_spec.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def self.load_object(ctx, id)
3333
end
3434

3535
def perform(ids)
36+
ids = ids.sort # for stable logging
3637
Backend.mget(ids).each_with_index do |item, idx|
3738
fulfill(ids[idx], item)
3839
end
@@ -80,7 +81,7 @@ def author(id:)
8081
end
8182

8283
def books_count(author_id:)
83-
# Of course this could be done without a load, but I want to test nested loaders
84+
# Of course this could be done without a nested load, but I want to test nested loaders
8485
BackendLoader.load_object(@context, author_id).then do |author|
8586
BackendLoader.load_all(@context, nil, author[:book_ids]).then do |books|
8687
books.size
@@ -166,7 +167,7 @@ def exec_query(*args)
166167
}')
167168

168169
expected_log = [
169-
'MGET ["b1", "a1"]',
170+
'MGET ["a1", "b1"]',
170171
'MGET ["b2"]'
171172
]
172173
assert_equal expected_log, log

0 commit comments

Comments
 (0)