Skip to content

Commit 067ab9c

Browse files
committed
Add dedicated object-related hooks
1 parent 337fd3c commit 067ab9c

6 files changed

Lines changed: 98 additions & 7 deletions

File tree

cop/development/trace_methods_cop.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class TraceMethodsCop < RuboCop::Cop::Base
3636
:execute_query,
3737
:execute_query_lazy,
3838
:lex,
39+
:object_loaded,
40+
:objects,
3941
:parse,
4042
:resolve_type,
4143
:resolve_type_lazy,

lib/graphql/execution/next/field_resolve_step.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,8 @@ def enqueue_next_steps
598598
if !@all_next_results.empty?
599599
@all_next_objects.compact!
600600

601+
query = @selections_step.query
602+
ctx = query.context
601603
if @static_type.kind.abstract?
602604
next_objects_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
603605
next_results_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
@@ -607,33 +609,35 @@ def enqueue_next_steps
607609
if (object_type = @runner.runtime_type_at[result])
608610
# OK
609611
else
610-
object_type = @runner.resolve_type(@static_type, next_object, @selections_step.query)
612+
object_type = @runner.resolve_type(@static_type, next_object, query)
611613
@runner.runtime_type_at[result] = object_type
612614
end
613615
next_objects_by_type[object_type] << next_object
614616
next_results_by_type[object_type] << result
615617
end
616618

617619
next_objects_by_type.each do |obj_type, next_objects|
620+
query.current_trace.objects(obj_type, next_objects, ctx)
618621
@runner.add_step(SelectionsStep.new(
619622
path: path,
620623
parent_type: obj_type,
621624
selections: @next_selections,
622625
objects: next_objects,
623626
results: next_results_by_type[obj_type],
624627
runner: @runner,
625-
query: @selections_step.query,
628+
query: query,
626629
))
627630
end
628631
else
632+
query.current_trace.objects(@static_type, @all_next_objects, ctx)
629633
@runner.add_step(SelectionsStep.new(
630634
path: path,
631635
parent_type: @static_type,
632636
selections: @next_selections,
633637
objects: @all_next_objects,
634638
results: @all_next_results,
635639
runner: @runner,
636-
query: @selections_step.query,
640+
query: query,
637641
))
638642
end
639643
end

lib/graphql/execution/next/load_argument_step.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def assign_value
4949
@loaded_value.path = @field_resolve_step.path
5050
@field_resolve_step.arguments = @loaded_value
5151
else
52+
query = @field_resolve_step.selections_step.query
53+
query.current_trace.object_loaded(@argument_definition, @loaded_value, query.context)
5254
@arguments[@argument_key] = @loaded_value
5355
end
5456

lib/graphql/execution/next/runner.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,14 @@ def begin_execute(isolated_steps, results, query, root_type, root_value)
261261

262262
results << { "data" => data }
263263

264+
objects = [root_value]
265+
query.current_trace.objects(root_type, objects, query.context)
266+
264267
if query.query?
265268
isolated_steps[0] << SelectionsStep.new(
266269
parent_type: root_type,
267270
selections: query.selected_operation.selections,
268-
objects: [root_value],
271+
objects: objects,
269272
results: [data],
270273
path: beginning_path,
271274
runner: self,
@@ -284,7 +287,7 @@ def begin_execute(isolated_steps, results, query, root_type, root_value)
284287
clobber: false, # `data` is being shared among several selections steps
285288
parent_type: root_type,
286289
selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
287-
objects: [root_value],
290+
objects: objects,
288291
results: [data],
289292
path: beginning_path,
290293
runner: self,
@@ -299,7 +302,7 @@ def begin_execute(isolated_steps, results, query, root_type, root_value)
299302
isolated_steps[0] << SelectionsStep.new(
300303
parent_type: root_type,
301304
selections: selected_operation.selections,
302-
objects: [root_value],
305+
objects: objects,
303306
results: [data],
304307
path: beginning_path,
305308
runner: self,
@@ -318,7 +321,7 @@ def begin_execute(isolated_steps, results, query, root_type, root_value)
318321
isolated_steps[0] << SelectionsStep.new(
319322
parent_type: resolved_type,
320323
selections: query.selected_operation.selections,
321-
objects: [root_value],
324+
objects: objects,
322325
results: [data],
323326
path: beginning_path,
324327
runner: self,

lib/graphql/tracing/trace.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ def authorized(query:, type:, object:)
9898
yield
9999
end
100100

101+
def objects(type, object, context)
102+
end
103+
104+
def object_loaded(argument_definition, object, context)
105+
end
106+
101107
# A call to `.authorized?` is starting
102108
# @param type [Class<GraphQL::Schema::Object>]
103109
# @param object [Object]

spec/graphql/tracing/trace_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,78 @@
88
superable_methods_source = superable_methods.map { |m| " #{m.inspect},\n" }.join
99
assert_includes trace_source, superable_methods_source
1010
end
11+
12+
13+
describe "object hooks" do
14+
class ObjectHooksSchema < GraphQL::Schema
15+
class Thing < GraphQL::Schema::Object
16+
field :name, String
17+
end
18+
19+
class Query < GraphQL::Schema::Object
20+
field :things, [Thing], resolve_static: true
21+
22+
def self.things(context)
23+
[OpenStruct.new(name: "Thing One"), OpenStruct.new(name: "Thing Two")]
24+
end
25+
26+
field :thing, Thing, resolve_static: true do
27+
argument :id, ID, loads: Thing, as: :thing
28+
end
29+
30+
def self.thing(context, thing:)
31+
thing
32+
end
33+
34+
field :thing_name, String, resolve_static: true do
35+
argument :thing_id, ID, loads: Thing
36+
end
37+
38+
def self.thing_name(context, thing:)
39+
thing.name
40+
end
41+
end
42+
43+
query(Query)
44+
use GraphQL::Execution::Next
45+
46+
def self.object_from_id(id, ctx)
47+
OpenStruct.new(name: "Thing ##{id}")
48+
end
49+
50+
def self.resolve_type(abs_type, obj, ctx)
51+
Thing
52+
end
53+
54+
module LogTrace
55+
def objects(type, objects, context)
56+
context[:log] ||= []
57+
context[:log] << "#{objects.size} objects as #{type.graphql_name}"
58+
super
59+
end
60+
61+
def object_loaded(argument_definition, object, context)
62+
context[:log] ||= []
63+
context[:log] << "#{argument_definition.path} loaded #{object.class}"
64+
super
65+
end
66+
end
67+
68+
trace_with(LogTrace)
69+
end
70+
71+
it "calls hooks with errors encountered during execution" do
72+
res = ObjectHooksSchema.execute_next("{ things { name } }")
73+
assert_equal ["Thing One", "Thing Two"], res["data"]["things"].map { |t| t["name"] }
74+
assert_equal ["1 objects as Query", "2 objects as Thing"], res.context[:log]
75+
76+
res = ObjectHooksSchema.execute_next("{ thing(id: \"5\") { name } }")
77+
assert_equal "Thing #5", res["data"]["thing"]["name"]
78+
assert_equal ["1 objects as Query", "Query.thing.id loaded OpenStruct", "1 objects as Thing"], res.context[:log]
79+
80+
res = ObjectHooksSchema.execute_next("{ thingName(thingId: \"77\") }")
81+
assert_equal "Thing #77", res["data"]["thingName"]
82+
assert_equal ["1 objects as Query", "Query.thingName.thingId loaded OpenStruct"], res.context[:log]
83+
end
84+
end
1185
end

0 commit comments

Comments
 (0)