Skip to content

Commit 3584553

Browse files
authored
Merge pull request #5582 from rmosolgo/exec-next-fixes
Exec-next: some runtime fixes
2 parents fa35e79 + dc3b87f commit 3584553

9 files changed

Lines changed: 33 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999

100100
- Runtime: add hooks for experimental custom runtimes #5425, #5429
101101
- Lazy handling and Dataloader have been merged under the hood #5422
102-
- Doc: merk `load_application_object_failed` as public #5426
102+
- Doc: mark `load_application_object_failed` as public #5426
103103

104104
# 2.5.11 (9 Jul 2025)
105105

guides/execution/migration.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ Adopting a feature flag system (described below) can also make this easier.
4848

4949
When all tests pass on `.execute_next`, you're ready to try it out in production.
5050

51-
## COMING SOON: Migration and Clean-Up Script
51+
## Migration and Clean-Up Script
5252

53-
Migrating field implementations can be automated in many cases. A script to analyze and execute these cases is in the works: [Pull Request](https://github.com/rmosolgo/graphql-ruby/pull/5531). This script will also be able to clean up unused instance methods when the migration is complete.
53+
`graphql_migrate_execution` is a command-line development tool that can automate many common GraphQL-Ruby field resolver patterns. Check out its docs and try out: https://rmosolgo.github.io/graphql_migrate_execution/
5454

5555
## Production Considerations
5656

@@ -159,7 +159,7 @@ Previously, GraphQL-Ruby would check `type_object.respond_to?(:title)`, `object.
159159

160160
Now, GraphQL-Ruby simply calls `object.title` and allows the `NoMethodError` to bubble up if one is raised.
161161

162-
### Query Analyzers, including complexity 🌕
162+
### Query Analyzers, including complexity 🟡
163163

164164
Support is identical; this runs before execution using the exact same code.
165165

@@ -219,7 +219,7 @@ Not supported yet. This will need some new kind of integration.
219219

220220
These methods/procs are called.
221221

222-
### `validates:`
222+
### `validates:` 🟡
223223

224224
Built-in validators are supported. Custom validators will always receive `nil` as the `object`. (`object` is no longer available; this API will probably change before this is fully released.)
225225

lib/graphql/execution/next/field_resolve_step.rb

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,19 @@ def append_selection(ast_node)
4949
end
5050

5151
def coerce_arguments(argument_owner, ast_arguments_or_hash, run_loads = true)
52-
arg_defns = argument_owner.arguments(@selections_step.query.context)
52+
arg_defns = @selections_step.query.types.arguments(argument_owner)
5353
if arg_defns.empty?
5454
return EmptyObjects::EMPTY_HASH
5555
end
5656
args_hash = {}
57+
5758
if ast_arguments_or_hash.nil? # This can happen with `.trigger`
5859
return args_hash
5960
end
6061

6162
arg_inputs_are_h = ast_arguments_or_hash.is_a?(Hash)
6263

63-
arg_defns.each do |arg_graphql_name, arg_defn|
64+
arg_defns.each do |arg_defn|
6465
arg_value = nil
6566
was_found = false
6667
if arg_inputs_are_h
@@ -279,14 +280,6 @@ def build_arguments
279280
query = @selections_step.query
280281
field_name = @ast_node.name
281282
@field_definition = query.get_field(@parent_type, field_name) || raise("Invariant: no field found for #{@parent_type.to_type_signature}.#{ast_node.name}")
282-
if field_name == "__typename"
283-
# TODO handle custom introspection
284-
@field_results = Array.new(@selections_step.objects.size, @parent_type.graphql_name)
285-
@object_is_authorized = AlwaysAuthorized
286-
build_results
287-
return
288-
end
289-
290283
arguments = coerce_arguments(@field_definition, @ast_node.arguments) # rubocop:disable Development/ContextIsPassedCop
291284
@arguments ||= arguments # may have already been set to an error
292285

@@ -727,6 +720,8 @@ def resolve_batch(objects, context, args_hash)
727720
obj_inst = @owner.wrap(obj_inst, context)
728721
end
729722
obj_inst.public_send(@field_definition.execution_next_mode_key, **args_hash)
723+
rescue GraphQL::ExecutionError => exec_err
724+
exec_err
730725
end
731726
else
732727
raise "Batching execution for #{path} not implemented (execution_next_mode: #{@execution_next_mode.inspect}); provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"

lib/graphql/execution/next/load_argument_step.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ def value
2020

2121
def call
2222
context = @field_resolve_step.selections_step.query.context
23-
@loaded_value = @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
23+
@loaded_value = begin
24+
@load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
25+
rescue GraphQL::UnauthorizedError => auth_err
26+
context.schema.unauthorized_object(auth_err)
27+
end
2428
if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
2529
runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
2630
else

lib/graphql/execution/next/prepare_object_step.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def authorize
7272
begin
7373
query.current_trace.begin_authorized(@resolved_type, @object, query.context)
7474
@authorized_value = @resolved_type.authorized?(@object, query.context)
75-
query.current_trace.end_authorized(@resolve_type, @object, query.context, @authorized_value)
75+
query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
7676
rescue GraphQL::UnauthorizedError => auth_err
7777
@authorization_error = auth_err
7878
end
@@ -83,7 +83,7 @@ def authorize
8383
else
8484
create_result
8585
end
86-
rescue GraphQL::Error => err
86+
rescue GraphQL::RuntimeError => err
8787
@graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
8888
end
8989

lib/graphql/execution/next/runner.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ def initialize(multiplex, authorization:)
2323
end
2424

2525
def resolve_type(type, object, query)
26-
query.current_trace.begin_resolve_type(@static_type, object, query.context)
26+
query.current_trace.begin_resolve_type(type, object, query.context)
2727
resolved_type, _ignored_new_value = query.resolve_type(type, object)
28-
query.current_trace.end_resolve_type(@static_type, object, query.context, resolved_type)
28+
query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
2929
resolved_type
3030
end
3131

@@ -199,7 +199,8 @@ def execute
199199
res_h
200200
end
201201

202-
GraphQL::Query::Result.new(query: query, values: fin_result)
202+
query.result_values = fin_result
203+
query.result
203204
end
204205
end
205206
ensure

lib/graphql/execution_error.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module GraphQL
66
class ExecutionError < GraphQL::RuntimeError
77
# @return [GraphQL::Language::Nodes::Field] the field where the error occurred
88
def ast_node
9-
ast_nodes.first
9+
ast_nodes&.first
1010
end
1111

1212
def ast_node=(new_node)

lib/graphql/schema/member/has_fields.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,14 @@ def field_class(new_field_class = nil)
150150

151151
def global_id_field(field_name, **kwargs)
152152
type = self
153-
field field_name, "ID", **kwargs, null: false
153+
field field_name, "ID", **kwargs, null: false, resolve_each: true
154154
define_method(field_name) do
155155
context.schema.id_from_object(object, type, context)
156156
end
157+
158+
define_singleton_method(field_name) do |object, context|
159+
context.schema.id_from_object(object, type, context)
160+
end
157161
end
158162

159163
# @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.

lib/graphql/schema/resolver.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ def call
6666
q = context.query
6767
trace_objs = [object]
6868
q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
69-
is_authed, new_return_value = authorized?(**@prepared_arguments)
69+
begin
70+
is_authed, new_return_value = authorized?(**@prepared_arguments)
71+
rescue GraphQL::UnauthorizedError => err
72+
new_return_value = q.schema.unauthorized_object(err)
73+
is_authed = true # the error was handled
74+
end
7075

7176
if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
7277
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)

0 commit comments

Comments
 (0)