Skip to content

Commit e9dfc23

Browse files
committed
Document, add tests for child field and grandchild field
1 parent 00952e8 commit e9dfc23

File tree

3 files changed

+86
-12
lines changed

3 files changed

+86
-12
lines changed

guides/queries/executing_queries.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,53 @@ end
123123

124124
Note that `context` is _not_ the hash that you passed it. It's an instance of {{ "GraphQL::Query::Context" | api_doc }}, but it delegates `#[]`, `#[]=`, and a few other methods to the hash you provide.
125125

126+
### Scoped Context
127+
128+
`context` is shared by the whole query. Anything you add to `context` will be accessible by any other field in the query (although GraphQL-Ruby's order of execution can vary).
129+
130+
However, "scoped context" is can be used to assign values into `context` that are only available in the current field and the _children_ of the current field. For example, in this query:
131+
132+
```graphql
133+
{
134+
posts {
135+
comments {
136+
author
137+
isOriginalPoster
138+
}
139+
}
140+
}
141+
```
142+
143+
You could use "scoped context" to implement `isOriginalPoster`, based on the parent `comments` field.
144+
145+
In `def comments`, add `:current_post` to scoped context using `context.scoped_set!`:
146+
147+
```ruby
148+
class Types::Post < Types::BaseObject
149+
# ...
150+
def comments
151+
context.scoped_set!(:current_post, object)
152+
object.comments
153+
end
154+
end
155+
```
156+
157+
Then, inside `User`, you can check `context[:current_post]`:
158+
159+
```ruby
160+
class Types::User < Types::BaseObject
161+
# ...
162+
def is_original_poster
163+
current_post = context[:current_post]
164+
current_post && current_post.author == object
165+
end
166+
end
167+
```
168+
169+
`context[:current_post]` will be present if an "upstream" field assigned it with `scoped_set!`.
170+
171+
`context.scoped_merge!({ ... })` is also available for setting multiple keys at once.
172+
126173
## Root Value
127174

128175
You can provide a root `object` value with `root_value:`. For example, to base the query off of the current organization:

lib/graphql/query/context.rb

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@ def initialize(query_context)
9797
@no_path = [].freeze
9898
end
9999

100-
def current_context
101-
@path_contexts[current_path]
100+
def merged_context
101+
merged_ctx = {}
102+
each_present_path_ctx do |path_ctx|
103+
merged_ctx = path_ctx.merge(merged_ctx)
104+
end
105+
merged_ctx
102106
end
103107

104108
def merge!(hash)
@@ -111,26 +115,26 @@ def current_path
111115
end
112116

113117
def key?(key)
114-
each_path_ctx do |path_ctx|
115-
if path_ctx && path_ctx.key?(key)
118+
each_present_path_ctx do |path_ctx|
119+
if path_ctx.key?(key)
116120
return true
117121
end
118122
end
119123
false
120124
end
121125

122126
def [](key)
123-
each_path_ctx do |path_ctx|
124-
if path_ctx && path_ctx.key?(key)
127+
each_present_path_ctx do |path_ctx|
128+
if path_ctx.key?(key)
125129
return path_ctx[key]
126130
end
127131
end
128132
nil
129133
end
130134

131135
def dig(key, *other_keys)
132-
each_path_ctx do |path_ctx|
133-
if path_ctx && path_ctx.key?(key)
136+
each_present_path_ctx do |path_ctx|
137+
if path_ctx.key?(key)
134138
found_value = path_ctx[key]
135139
if other_keys.any?
136140
return found_value.dig(*other_keys)
@@ -146,12 +150,17 @@ def dig(key, *other_keys)
146150

147151
# Start at the current location,
148152
# but look up the tree for previously-assigned scoped values
149-
def each_path_ctx
153+
def each_present_path_ctx
150154
search_path = current_path.dup
151-
yield(@path_contexts[search_path])
155+
if (current_path_ctx = @path_contexts[search_path])
156+
yield(current_path_ctx)
157+
end
158+
152159
while search_path.size > 0
153160
search_path.pop # look one level higher
154-
yield(@path_contexts[search_path])
161+
if (search_path_ctx = @path_contexts[search_path])
162+
yield(search_path_ctx)
163+
end
155164
end
156165
end
157166
end
@@ -225,7 +234,7 @@ def dig(key, *other_keys)
225234
end
226235

227236
def to_h
228-
if (current_scoped_context = @scoped_context.current_context)
237+
if (current_scoped_context = @scoped_context.merged_context)
229238
@provided_values.merge(current_scoped_context)
230239
else
231240
@provided_values

spec/graphql/query/context_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,24 @@ class ContextSchema < GraphQL::Schema
407407
assert(context.key?(expected_key))
408408
assert_equal(expected_value, context.fetch(expected_key))
409409
assert_equal(expected_value, context.dig(expected_key)) if RUBY_VERSION >= '2.3.0'
410+
411+
# Enter a child field:
412+
context.namespace(:interpreter)[:current_path] = ["somewhere", "child"]
413+
assert_nil(context[expected_key])
414+
assert_equal({ expected_key => nil }, context.to_h)
415+
assert(context.key?(expected_key))
416+
assert_nil(context.fetch(expected_key))
417+
assert_nil(context.dig(expected_key)) if RUBY_VERSION >= '2.3.0'
418+
419+
# And a grandchild field
420+
context.namespace(:interpreter)[:current_path] = ["somewhere", "child", "grandchild"]
421+
context.scoped_set!(expected_key, :something_else)
422+
context.scoped_set!(:another_key, :another_value)
423+
assert_equal(:something_else, context[expected_key])
424+
assert_equal({ expected_key => :something_else, another_key: :another_value }, context.to_h)
425+
assert(context.key?(expected_key))
426+
assert_equal(:something_else, context.fetch(expected_key))
427+
assert_equal(:something_else, context.dig(expected_key)) if RUBY_VERSION >= '2.3.0'
410428
end
411429

412430
it "sets a value using #scoped_set!" do

0 commit comments

Comments
 (0)