Skip to content

Commit ee1b36c

Browse files
committed
Update docs
1 parent 1ba0baa commit ee1b36c

2 files changed

Lines changed: 111 additions & 59 deletions

File tree

guides/execution/migration.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,20 @@ render json: result.to_h
145145

146146
Performance improvements in batching execution come at the cost of removing support for many "nice-to-have" features in GraphQL-Ruby by default. Those features are addressed here.
147147

148+
### Implicit Field Resolution
149+
150+
The _default_, _implicit_ field resolution behavior has changed. Previously, when a field didn't have a specified method or hash key, GraphQL-Ruby would try a combination of `object.public_send(...)` and `object[...]` to resolve it. In `Execution::Next`, GraphQL-Ruby tries `object.public_send(field_sym)` unless another configuration is provided. This removes a lot of overhead from field execution.
151+
152+
Consider a field like this:
153+
154+
```ruby
155+
field :title, String
156+
```
157+
158+
Previously, GraphQL-Ruby would check `type_object.respond_to?(:title)`, `object.respond_to?(:title)`, `object.is_a?(Hash)`. `object.key?(:title)` and `object.key?("title")`.
159+
160+
Now, GraphQL-Ruby simply calls `object.title` and allows the `NoMethodError` to bubble up if one is raised.
161+
148162
### Query Analyzers, including complexity 🌕
149163

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

guides/execution/next.md

Lines changed: 97 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -44,88 +44,125 @@ See {% internal_link "compatibility notes", "/execution/migration#compatibility-
4444

4545
## Field configurations
4646

47-
The new runtime engine supports several field resolution configurations out of the box:
47+
The new runtime engine supports several field resolution configurations out of the box.
4848

49-
- __Method calls__: fields that call `object.#{field_name}`. This is the default, and the method name can be overridden with `method: ...`:
49+
### Method calls (default, `method:`)
5050

51-
```ruby
52-
field :title, String # calls object.title
53-
field :title, String, method: :get_title_somehow # calls object.get_title_somehow
54-
```
55-
- __Hash keys__: fields that call `object[hash_key]`, configured with `hash_key: ...`.
51+
Fields that call `object.#{field_name}`. This is the default, and the method name can be overridden with `method: ...`:
5652

53+
```ruby
54+
field :title, String # calls object.title
55+
field :title, String, method: :get_title_somehow # calls object.get_title_somehow
56+
```
5757

58-
```ruby
59-
field :title, String, hash_key: :title # calls object[:title]
60-
field :title, String, hash_key: "title" # calls object["title"]
61-
```
58+
### Hash keys (`hash_key:`)
6259

63-
(Note: new execution doesn't "fall back" to hash key lookups, and it doesn't try strings when Symbols are given. The existing runtime engine does that...)
60+
Fields that call `object[hash_key]`, configured with `hash_key: ...`.
6461

65-
- __Batch resolvers__: fields that use a _class method_ to map parent objects to field results, configured with `resolve_batch:`:
62+
```ruby
63+
field :title, String, hash_key: :title # calls object[:title]
64+
field :title, String, hash_key: "title" # calls object["title"]
65+
```
6666

67-
```ruby
68-
field :title, String, resolve_batch: :titles do
69-
argument :language, Types::Language, required: false, default_value: "EN"
70-
end
67+
(Note: new execution doesn't "fall back" to hash key lookups, and it doesn't try strings when Symbols are given. The existing runtime engine does that...)
7168

72-
def self.titles(objects, context, language:)
73-
# This is equivalent to plain `field :title, ...`, but for example:
74-
objects.map { |obj| obj.title(language:) }
75-
end
76-
```
69+
### Per-object (`resolve_each:`)
7770

78-
This is especially useful when batching Dataloader calls:
71+
These fields use a _class method_ to produce a result for each parent object, configured with `resolve_each:`.
7972

80-
```ruby
81-
class Types::Comment < BaseObject
82-
field :post, Types::Post, resolve_batch: :posts
73+
```ruby
74+
field :title, String, resolve_each: :title do
75+
argument :language, Types::Language, required: false, default_value: "EN"
76+
end
8377

84-
# Use `.load_all(ids)` to fetch all in a single round-trip
85-
def self.posts(objects, context)
86-
# TODO: add a shorthand for this in GraphQL-Ruby
87-
context.dataloader
88-
.with(GraphQL::Dataloader::ActiveRecordSource)
89-
.load_all(objects.map(&:post_id))
90-
end
91-
end
92-
```
78+
def self.title(object, context, language:)
79+
# Assuming this makes no database lookups or other external service calls:
80+
object.localization.get(:title, language:)
81+
end
82+
```
9383

94-
- __Each resolvers__: fields that use a _class method_ to produce a result for each parent object, configured with `resolve_each:`. This is similar to `resolve_batch:`, except you never receive the whole list of `objects`:
84+
Under the hood, GraphQL-Ruby calls `objects.map { ... }`, calling this class method.
9585

96-
```ruby
97-
field :title, String, resolve_each: :title do
98-
argument :language, Types::Language, required: false, default_value: "EN"
99-
end
86+
‼️ __Don't use this__ if your logic calls external services or databases (including with Dataloader). If you do, your I/O will be sequential instead of batched. Use `resolve_batch:` or `resolve_static:` instead, see below.
10087

101-
def self.title(object, context, language:)
102-
object.title(language:)
103-
end
104-
```
88+
### Global (`resolve_static:`)
10589

106-
(Under the hood, GraphQL-Ruby calls `objects.map { ... }`, calling this class method.)
90+
Fields that use a _class method_ to produce a single result shared by all objects, configured with `resolve_static:`. The method does _not_ receive any `object`, only `context`:
10791

108-
- __Static resolvers__: fields that use a _class method_ to produce a single result shared by all objects, configured with `resolve_static:`. The method does _not_ receive any `object`, only `context`:
92+
```ruby
93+
field :posts_count, Integer, resolve_static: :count_all_posts do
94+
argument :include_unpublished, Boolean, required: false, default_value: false
95+
end
10996

110-
```ruby
111-
field :posts_count, Integer, resolve_static: :count_all_posts do
112-
argument :include_unpublished, Boolean, required: false, default_value: false
113-
end
97+
def self.count_all_posts(context, include_unpublished:)
98+
posts = Post.all
99+
if !include_unpublished
100+
posts = posts.published
101+
end
102+
posts.count
103+
end
104+
```
105+
106+
Under the hood, GraphQL-Ruby calls `Array.new(objects.size, static_result)`.
114107

115-
def self.count_all_posts(context, include_unpublished:)
116-
posts = Post.all
117-
if !include_unpublished
118-
posts = posts.published
119-
end
120-
posts.count
108+
### Batch resolvers (`resolve_batch:`)
109+
110+
This is a high-performance option for when you need to do I/O to generate results. By working with a batch of objects, you can greatly reduce the framework overhead in preparing a result.
111+
112+
These fields use a _class method_ to map parent objects to field results, configured with `resolve_batch:`:
113+
114+
```ruby
115+
field :title, String, resolve_batch: :titles do
116+
argument :language, Types::Language, required: false, default_value: "EN"
117+
end
118+
119+
def self.titles(objects, context, language:)
120+
# This is equivalent to plain `field :title, ...`, but for example:
121+
objects.map { |obj| obj.title(language:) }
122+
end
123+
```
124+
125+
This is especially useful when batching Dataloader calls:
126+
127+
```ruby
128+
class Types::Comment < BaseObject
129+
field :post, Types::Post, resolve_batch: :posts
130+
131+
# Use `.load_all(ids)` to fetch all in a single round-trip
132+
def self.posts(objects, context)
133+
# TODO: add a shorthand for this in GraphQL-Ruby
134+
context.dataloader
135+
.with(GraphQL::Dataloader::ActiveRecordSource)
136+
.load_all(objects.map(&:post_id))
121137
end
122-
```
138+
end
139+
```
140+
141+
### Legacy instance methods
142+
143+
`resolve_legacy_instance_method:`
144+
145+
There is _partial_ support for instance methods on Object type classes, for now. It will be deprecated and removed soon.
146+
147+
‼️ Don't use legacy instance methods with Dataloader. It will be sequential, not batched. ‼️
148+
149+
```ruby
150+
field :title, String, resolve_legacy_instance_method: true do
151+
argument :language, Types::Language, required: false, default_value: "EN"
152+
end
153+
154+
def title(language:)
155+
# Assuming this makes no database lookups or other external service calls:
156+
object.localization.get(:title, language:)
157+
end
158+
```
159+
160+
Under the hood, GraphQL-Ruby calls `objects.map { ... }`, calling this instance method.
123161

124-
(Under the hood, GraphQL-Ruby calls `Array.new(objects.size, static_result)`)
125162

126163
### `true` shorthand
127164

128-
There is also a `true` shorthand: when one of the `resolve_...:` configurations is passed as `true` (ie, `resolve_batch: true`, `resolve_each: true`, or `resolve_static: true`), then the Symbol field name is used as the class method. For example:
165+
There is also a `true` shorthand: when one of the `resolve_...:` configurations is passed as `true` (ie, `resolve_batch: true`, `resolve_each: true`, `resolve_static: true`, or `resolve_legacy_instance_method: true`), then the Symbol field name is used as the class method. For example:
129166

130167
```ruby
131168
field :posts_count, Integer, resolve_static: true
@@ -135,6 +172,7 @@ def self.posts_count(context)
135172
end
136173
```
137174

175+
138176
## Migration
139177

140178
Read about migrating in the {% internal_link "Migration Doc", "/execution/migration" %}.

0 commit comments

Comments
 (0)