Skip to content

Commit 974be7e

Browse files
authored
Merge pull request rmosolgo#679 from rmosolgo/connection-instrumentation
refactor(Relay) wrap returned objects with instrumentation
2 parents 1711d71 + b9efb9e commit 974be7e

File tree

10 files changed

+83
-36
lines changed

10 files changed

+83
-36
lines changed

lib/graphql/compatibility/lazy_execution_specification.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ def test_it_calls_lazy_resolve_instrumentation
167167
"PUSH",
168168
"Query.push: 1",
169169
"Query.push: 2",
170+
"Query.pushes: [1, 2, 3]",
170171
"PUSH",
171172
"LazyPush.push: 3",
172173
"LazyPushEdge.node: 1",

lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def initialize(ctx, values)
3737
def push
3838
@values.map { |v| LazyPush.new(@ctx, v) }
3939
end
40+
41+
def value
42+
@values
43+
end
4044
end
4145

4246
module LazyInstrumentation

lib/graphql/define/assign_connection.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ module Define
44
module AssignConnection
55
def self.call(type_defn, *field_args, max_page_size: nil, **field_kwargs, &field_block)
66
underlying_field = GraphQL::Define::AssignObjectField.call(type_defn, *field_args, **field_kwargs, &field_block)
7-
connection_field = GraphQL::Relay::ConnectionField.create(underlying_field, max_page_size: max_page_size)
8-
type_defn.fields[underlying_field.name] = connection_field
7+
underlying_field.connection_max_page_size = max_page_size
8+
underlying_field.connection = true
9+
type_defn.fields[underlying_field.name] = underlying_field
910
end
1011
end
1112
end

lib/graphql/field.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,25 @@ class Field
177177
# @return [Object, GraphQL::Function] The function used to derive this field
178178
attr_accessor :function
179179

180+
attr_writer :connection
181+
182+
# @return [Boolean]
183+
def connection?
184+
@connection
185+
end
186+
187+
# @return [nil, Integer]
188+
attr_accessor :connection_max_page_size
189+
180190
def initialize
181191
@complexity = 1
182192
@arguments = {}
183193
@resolve_proc = build_default_resolver
184194
@lazy_resolve_proc = DefaultLazyResolve
185195
@relay_node_field = false
186196
@default_arguments = nil
197+
@connection = false
198+
@connection_max_page_size = nil
187199
end
188200

189201
def initialize_copy(other)

lib/graphql/relay.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
require 'graphql/relay/global_id_resolve'
1212
require 'graphql/relay/mutation'
1313
require 'graphql/relay/node'
14-
require 'graphql/relay/connection_field'
14+
require 'graphql/relay/connection_instrumentation'
1515
require 'graphql/relay/connection_resolve'
1616
require 'graphql/relay/connection_type'

lib/graphql/relay/connection_field.rb renamed to lib/graphql/relay/connection_instrumentation.rb

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
module GraphQL
33
module Relay
44
# Provided a GraphQL field which returns a collection of nodes,
5-
# `ConnectionField.create` modifies that field to expose those nodes
6-
# as a collection.
5+
# wrap that field to expose those nodes as a connection.
76
#
87
# The original resolve proc is used to fetch nodes,
98
# then a connection implementation is fetched with {BaseConnection.connection_for_nodes}.
10-
class ConnectionField
9+
module ConnectionInstrumentation
1110
ARGUMENT_DEFINITIONS = [
1211
["first", GraphQL::INT_TYPE, "Returns the first _n_ elements from the list."],
1312
["after", GraphQL::STRING_TYPE, "Returns the elements in the list that come after the specified global ID."],
@@ -28,14 +27,21 @@ class ConnectionField
2827
# Build a connection field from a {GraphQL::Field} by:
2928
# - Merging in the default arguments
3029
# - Transforming its resolve function to return a connection object
31-
# @param underlying_field [GraphQL::Field] A field which returns nodes to be wrapped as a connection
32-
# @param max_page_size [Integer] The maximum number of nodes which may be requested (if a larger page is requested, it is limited to this number)
33-
# @return [GraphQL::Field] A redefined field with connection behavior
34-
def self.create(underlying_field, max_page_size: nil)
35-
connection_arguments = DEFAULT_ARGUMENTS.merge(underlying_field.arguments)
36-
original_resolve = underlying_field.resolve_proc
37-
connection_resolve = GraphQL::Relay::ConnectionResolve.new(underlying_field, original_resolve, max_page_size: max_page_size)
38-
underlying_field.redefine(resolve: connection_resolve, arguments: connection_arguments)
30+
def self.instrument(type, field)
31+
if field.connection?
32+
connection_arguments = DEFAULT_ARGUMENTS.merge(field.arguments)
33+
original_resolve = field.resolve_proc
34+
original_lazy_resolve = field.lazy_resolve_proc
35+
connection_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_resolve)
36+
connection_lazy_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_lazy_resolve)
37+
field.redefine(
38+
resolve: connection_resolve,
39+
lazy_resolve: connection_lazy_resolve,
40+
arguments: connection_arguments,
41+
)
42+
else
43+
field
44+
end
3945
end
4046
end
4147
end

lib/graphql/relay/connection_resolve.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22
module GraphQL
33
module Relay
44
class ConnectionResolve
5-
def initialize(field, underlying_resolve, max_page_size: nil)
5+
def initialize(field, underlying_resolve)
66
@field = field
77
@underlying_resolve = underlying_resolve
8-
@max_page_size = max_page_size
8+
@max_page_size = field.connection_max_page_size
99
end
1010

1111
def call(obj, args, ctx)
1212
nodes = @underlying_resolve.call(obj, args, ctx)
1313
if ctx.schema.lazy?(nodes)
14-
@field.prepare_lazy(nodes, args, ctx).then { |resolved_nodes|
15-
build_connection(resolved_nodes, args, obj, ctx)
16-
}
14+
nodes
1715
else
1816
build_connection(nodes, args, obj, ctx)
1917
end

lib/graphql/relay/mutation.rb

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def has_generated_return_type?
122122
end
123123

124124
def resolve=(new_resolve_proc)
125-
@resolve_proc = MutationResolve.new(self, new_resolve_proc, wrap_result: has_generated_return_type?)
125+
@resolve_proc = new_resolve_proc
126126
end
127127

128128
def field
@@ -218,20 +218,34 @@ def self.define_subclass(mutation_defn)
218218
end
219219
end
220220

221+
module MutationInstrumentation
222+
def self.instrument(type, field)
223+
if field.mutation
224+
new_resolve = MutationResolve.new(field.mutation, field.resolve_proc)
225+
new_lazy_resolve = MutationResolve.new(field.mutation, field.lazy_resolve_proc)
226+
field.redefine(resolve: new_resolve, lazy_resolve: new_lazy_resolve)
227+
else
228+
field
229+
end
230+
end
231+
end
232+
221233
class MutationResolve
222-
def initialize(mutation, resolve, wrap_result:)
234+
def initialize(mutation, resolve)
223235
@mutation = mutation
224236
@resolve = resolve
225-
@wrap_result = wrap_result
237+
@wrap_result = mutation.has_generated_return_type?
226238
end
227239

228240
def call(obj, args, ctx)
229-
mutation_result = @resolve.call(obj, args[:input], ctx)
241+
begin
242+
mutation_result = @resolve.call(obj, args[:input], ctx)
243+
rescue GraphQL::ExecutionError => err
244+
mutation_result = err
245+
end
230246

231247
if ctx.schema.lazy?(mutation_result)
232-
@mutation.field.prepare_lazy(mutation_result, args, ctx).then { |inner_obj|
233-
build_result(inner_obj, args, ctx)
234-
}
248+
mutation_result
235249
else
236250
build_result(mutation_result, args, ctx)
237251
end

lib/graphql/schema.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,15 @@ def rescue_middleware
433433

434434
private
435435

436+
# Apply instrumentation to fields. Relay instrumentation is applied last
437+
# so that user-provided instrumentation can wrap user-provided resolve functions,
438+
# _then_ Relay helpers can wrap the returned objects.
436439
def build_instrumented_field_map
437-
@instrumented_field_map = InstrumentedFieldMap.new(self, @instrumenters[:field])
440+
all_instrumenters = @instrumenters[:field] + [
441+
GraphQL::Relay::ConnectionInstrumentation,
442+
GraphQL::Relay::Mutation::MutationInstrumentation,
443+
]
444+
@instrumented_field_map = InstrumentedFieldMap.new(self, all_instrumenters)
438445
end
439446

440447
def build_types_map

spec/graphql/relay/connection_field_spec.rb renamed to spec/graphql/relay/connection_instrumentation_spec.rb

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22
require "spec_helper"
33

4-
describe GraphQL::Relay::ConnectionField do
4+
describe GraphQL::Relay::ConnectionInstrumentation do
55
it "replaces the previous field definition" do
66
test_type = GraphQL::ObjectType.define do
77
name "Test"
@@ -16,7 +16,7 @@
1616
assert_instance_of StarWars::ShipsWithMaxPageSize, conn_field.function
1717
end
1818

19-
it "leaves the original field untouched" do
19+
let(:build_schema) {
2020
test_type = nil
2121

2222
test_field = GraphQL::Field.define do
@@ -29,7 +29,15 @@
2929
connection :tests, test_field
3030
end
3131

32-
conn_field = test_type.fields["tests"]
32+
[
33+
test_field,
34+
GraphQL::Schema.define(query: test_type, raise_definition_error: true)
35+
]
36+
}
37+
38+
it "leaves the original field untouched" do
39+
test_field, test_schema = build_schema
40+
conn_field = test_schema.get_field(test_schema.query, "tests")
3341

3442
assert_equal 0, test_field.arguments.length
3543
assert_equal 4, conn_field.arguments.length
@@ -39,12 +47,8 @@
3947
end
4048

4149
it "passes connection behaviors to redefinitions" do
42-
test_type = GraphQL::ObjectType.define do
43-
name "Test"
44-
connection :tests, test_type.connection_type
45-
end
46-
47-
connection_field = test_type.fields["tests"]
50+
_test_field, test_schema = build_schema
51+
connection_field = test_schema.get_field(test_schema.query, "tests")
4852
redefined_connection_field = connection_field.redefine { argument "name", types.String }
4953

5054
assert_equal 4, connection_field.arguments.size

0 commit comments

Comments
 (0)