Skip to content

Commit

Permalink
Use extended instead of originals in after_resolve
Browse files Browse the repository at this point in the history
Field Extensions let you extend (modify) the resolver object and
arguments in the `resolve` hook. Previously the `after_resolve` hook
would receive the *original* object and arguments and not the extended
ones. This was a lossy process with no way for `after_resolve` to access
the extended values.

This changes `after_resolve` to receive the extended values instead. If
you need access to the original non-extended values, the `memo` argument
can be used.

Example:

```ruby
class MyExtension < GraphQL::Schema::FieldExtension
  def apply(field)
    field.argument(:my_new_arg, :string, required: false)
  end

  def resolve(object:, arguments:, context:)
    next_args = arguments.dup
    next_args.delete(:my_new_arg)
    yield(object, next_args, { original_arguments: arguments })
  end

  def after_resolve(value:, context:, arguments:, memo:, **_args)
    # `arguments` => extended/modified arguments
    # `memo` => hash with `original_arguments`
  end
end
```
  • Loading branch information
swalkinshaw committed Sep 29, 2020
1 parent 183f318 commit 53330be
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 13 deletions.
13 changes: 7 additions & 6 deletions lib/graphql/schema/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -725,20 +725,21 @@ def with_extensions(obj, args, ctx)
if @extensions.empty?
yield(obj, args)
else
# Save these so that the originals can be re-given to `after_resolve` handlers.
original_args = args
original_obj = obj
extended_obj = obj
extended_args = args

memos = []
value = run_extensions_before_resolve(memos, obj, args, ctx) do |extended_obj, extended_args|
yield(extended_obj, extended_args)
value = run_extensions_before_resolve(memos, obj, args, ctx) do |obj, args|
extended_obj = obj
extended_args = args
yield(obj, args)
end

ctx.schema.after_lazy(value) do |resolved_value|
@extensions.each_with_index do |ext, idx|
memo = memos[idx]
# TODO after_lazy?
resolved_value = ext.after_resolve(object: original_obj, arguments: original_args, context: ctx, value: resolved_value, memo: memo)
resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
end
resolved_value
end
Expand Down
15 changes: 8 additions & 7 deletions lib/graphql/schema/field/connection_extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ def resolve(object:, arguments:, context:)
next_args.delete(:last)
next_args.delete(:before)
next_args.delete(:after)
yield(object, next_args)
yield(object, next_args, arguments)
end

def after_resolve(value:, object:, arguments:, context:, memo:)
original_arguments = memo
# rename some inputs to avoid conflicts inside the block
maybe_lazy = value
value = nil
Expand All @@ -37,10 +38,10 @@ def after_resolve(value:, object:, arguments:, context:, memo:)
# update the connection with some things that may not have been provided
value.context ||= context
value.parent ||= object.object
value.first_value ||= arguments[:first]
value.after_value ||= arguments[:after]
value.last_value ||= arguments[:last]
value.before_value ||= arguments[:before]
value.first_value ||= original_arguments[:first]
value.after_value ||= original_arguments[:after]
value.last_value ||= original_arguments[:last]
value.before_value ||= original_arguments[:before]
if field.has_max_page_size? && !value.has_max_page_size_override?
value.max_page_size = field.max_page_size
end
Expand All @@ -50,15 +51,15 @@ def after_resolve(value:, object:, arguments:, context:, memo:)
value
elsif context.schema.new_connections?
wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
context.schema.connections.wrap(field, object.object, value, arguments, context, wrappers: wrappers)
context.schema.connections.wrap(field, object.object, value, original_arguments, context, wrappers: wrappers)
else
if object.is_a?(GraphQL::Schema::Object)
object = object.object
end
connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
connection_class.new(
value,
arguments,
original_arguments,
field: field,
max_page_size: field.max_page_size,
parent: object,
Expand Down
31 changes: 31 additions & 0 deletions spec/graphql/schema/field_extension_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ def resolve(object:, arguments:, context:)
end
end

class MultiplyByArgumentUsingAfterResolve < GraphQL::Schema::FieldExtension
def apply
field.argument(:factor, Integer, required: true)
end

def resolve(object:, arguments:, context:)
original_arguments = arguments.dup
arguments.delete(:factor)
yield(object, arguments, { original_arguments: original_arguments})
end

def after_resolve(object:, value:, arguments:, context:, memo:)
value * memo.dig(:original_arguments, :factor)
end
end

class BaseObject < GraphQL::Schema::Object
end

Expand Down Expand Up @@ -89,6 +105,16 @@ def pass_thru(input:, **args)
input # return it as-is, it will be modified by extensions
end

field :multiply_input3, Integer, null: false, resolver_method: :pass_thru_without_splat, extensions: [MultiplyByArgumentUsingAfterResolve] do
argument :input, Integer, required: true
end

# lack of kwargs splat demonstrates the extended arguments are passed to the resolver method
def pass_thru_without_splat(input:)
binding.pry
input
end

field :multiple_extensions, Integer, null: false, resolver_method: :pass_thru,
extensions: [DoubleFilter, { MultiplyByOption => { factor: 3 } }] do
argument :input, Integer, required: true
Expand Down Expand Up @@ -151,6 +177,11 @@ def exec_query(query_str, **kwargs)
assert_equal 15, res["data"]["multiplyInput"]
end

it "calls the resolver method with the extended arguments" do
res = exec_query("{ multiplyInput3(input: 3, factor: 5) }")
assert_equal 15, res["data"]["multiplyInput3"]
end

it "supports multiple extensions via extensions kwarg" do
# doubled then multiplied by 3 specified via option
res = exec_query("{ multipleExtensions(input: 3) }")
Expand Down

0 comments on commit 53330be

Please sign in to comment.