Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a schema-level hook for lazy/batched resolution #1784

Merged
merged 4 commits into from
Sep 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/graphql/execution/execute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ def resolve_field(object, field_ctx)
# If the returned object is finished, continue to coerce
# and resolve child fields
def continue_or_wait(raw_value, field_type, field_ctx)
if (lazy_method = field_ctx.schema.lazy_method_name(raw_value))
if field_ctx.schema.lazy?(raw_value)
field_ctx.value = Execution::Lazy.new {
inner_value = begin
begin
raw_value.public_send(lazy_method)
field_ctx.schema.sync_lazy(raw_value)
rescue GraphQL::UnauthorizedError => err
field_ctx.schema.unauthorized_object(err)
end
Expand Down
8 changes: 1 addition & 7 deletions lib/graphql/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,7 @@ def build_default_resolver

module DefaultLazyResolve
def self.call(obj, args, ctx)
method_name = ctx.schema.lazy_method_name(obj)
next_obj = obj.public_send(method_name)
if ctx.schema.lazy?(next_obj)
call(next_obj, args, ctx)
else
next_obj
end
ctx.schema.sync_lazy(obj)
end
end
end
Expand Down
28 changes: 25 additions & 3 deletions lib/graphql/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ class << self
:static_validator, :introspection_system,
:query_analyzers, :tracers, :instrumenters,
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
:validate, :multiplex_analyzers, :lazy?, :lazy_method_name, :after_lazy,
:validate, :multiplex_analyzers, :lazy?, :lazy_method_name, :after_lazy, :sync_lazy,
# Configuration
:max_complexity=, :max_depth=,
:metadata,
Expand Down Expand Up @@ -1002,9 +1002,9 @@ def resolve_type(type, obj, ctx = :__undefined__)
# - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
# @api private
def after_lazy(value)
if (lazy_method = lazy_method_name(value))
if lazy?(value)
GraphQL::Execution::Lazy.new do
result = value.public_send(lazy_method)
result = sync_lazy(value)
# The returned result might also be lazy, so check it, too
after_lazy(result) do |final_result|
yield(final_result) if block_given?
Expand All @@ -1015,6 +1015,28 @@ def after_lazy(value)
end
end

# Override this method to handle lazy objects in a custom way.
# @param value [Object] an instance of a class registered with {.lazy_resolve}
# @param ctx [GraphQL::Query::Context] the context for this query
# @return [Object] A GraphQL-ready (non-lazy) object
def self.sync_lazy(value)
yield(value)
end

# @see Schema.sync_lazy for a hook to override
# @api private
def sync_lazy(value)
self.class.sync_lazy(value) { |v|
lazy_method = lazy_method_name(v)
if lazy_method
synced_value = value.public_send(lazy_method)
sync_lazy(synced_value)
else
v
end
}
end

protected

def rescues?
Expand Down
19 changes: 18 additions & 1 deletion spec/graphql/execution/lazy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@
end
end

describe "Schema#sync_lazy(object)" do
it "Passes objects to that hook at runtime" do
res = run_query <<-GRAPHQL
{
a: nullableNestedSum(value: 1001) { value }
b: nullableNestedSum(value: 1013) { value }
c: nullableNestedSum(value: 1002) { value }
}
GRAPHQL

# This odd, non-adding behavior is hacked into `#sync_lazy`
assert_equal 101, res["data"]["a"]["value"]
assert_equal 113, res["data"]["b"]["value"]
assert_equal 102, res["data"]["c"]["value"]
end
end

describe "LazyMethodMap" do
class SubWrapper < LazyHelpers::Wrapper; end

Expand All @@ -165,7 +182,7 @@ class SubWrapper < LazyHelpers::Wrapper; end
map.set(LazyHelpers::SumAll, :value)
b = LazyHelpers::Wrapper.new(1)
sub_b = LazyHelpers::Wrapper.new(2)
s = LazyHelpers::SumAll.new({}, 3)
s = LazyHelpers::SumAll.new(3)
assert_equal(:item, map.get(b))
assert_equal(:item, map.get(sub_b))
assert_equal(:value, map.get(s))
Expand Down
17 changes: 13 additions & 4 deletions spec/support/lazy_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class SumAll
attr_reader :own_value
attr_writer :value

def initialize(ctx, own_value)
def initialize(own_value)
@own_value = own_value
all << self
end
Expand Down Expand Up @@ -56,7 +56,7 @@ def nested_sum(value:)
if value == 13
Wrapper.new(nil)
else
SumAll.new(@context, @object + value)
SumAll.new(@object + value)
end
end

Expand All @@ -82,7 +82,7 @@ def nested_sum(value:)

field :nestedSum, !LazySum do
argument :value, !types.Int
resolve ->(o, args, c) { SumAll.new(c, args[:value]) }
resolve ->(o, args, c) { SumAll.new(args[:value]) }
end

field :nullableNestedSum, LazySum do
Expand All @@ -91,7 +91,7 @@ def nested_sum(value:)
if args[:value] == 13
Wrapper.new { raise GraphQL::ExecutionError.new("13 is unlucky") }
else
SumAll.new(c, args[:value])
SumAll.new(args[:value])
end
}
end
Expand Down Expand Up @@ -142,6 +142,15 @@ class LazySchema < GraphQL::Schema
instrument(:query, SumAllInstrumentation.new(counter: nil))
instrument(:multiplex, SumAllInstrumentation.new(counter: 1))
instrument(:multiplex, SumAllInstrumentation.new(counter: 2))

def self.sync_lazy(lazy)
if lazy.is_a?(SumAll) && lazy.own_value > 1000
lazy.value # clear the previous set
lazy.own_value - 900
else
super
end
end
end

def run_query(query_str)
Expand Down