-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Field Extensions (second try) #1758
Merged
Merged
Changes from 10 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
819329a
Add basic field filter flow
rmosolgo d6f1df2
Add API docs
rmosolgo 4471a09
Add Field#to_options for duping fields
rmosolgo f06ab79
Migrate connections to field filters
rmosolgo 0a77f51
Refactor scoping to be a filter
rmosolgo 7d09272
Remove field #to_options
rmosolgo 4fa7ebd
Simplify filter API
rmosolgo 7715a4a
rename FieldFilter -> FieldExtension
rmosolgo e0598b7
Add a guide
rmosolgo da7b3dd
Use compatible heredoc
rmosolgo c2353a0
Merge branch 'master' into field-filter-instances
rmosolgo 8706550
Add #apply hook
rmosolgo 28b6945
Update connection extension
rmosolgo f64e331
rewrite with_extensions for an early default case iterative style
rmosolgo 333dd6d
fix guide typo
rmosolgo ddee951
pass the original obj & args to after_resolve
rmosolgo 46db5a9
Return the after-resolve'd value
rmosolgo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
--- | ||
layout: guide | ||
doc_stub: false | ||
search: true | ||
section: Type Definitions | ||
title: Field Extensions | ||
desc: Programmatically modify field configuration and resolution | ||
index: 10 | ||
class_based_api: true | ||
--- | ||
|
||
{{ "GraphQL::Schema::FieldExtension" | api_doc }} provides a way to modify user-defined fields in a programmatic way. For example, Relay connections may are implemented as a field extension. | ||
|
||
### Making a new extension | ||
|
||
Field extensions are subclasses of {{ "GraphQL::Schema::FieldExtension" | api_doc }}: | ||
|
||
```ruby | ||
class MyExtension < GraphQL::Schema::FieldExtension | ||
end | ||
``` | ||
|
||
### Using an extension | ||
|
||
Defined extensions can be added to fields using the `extensions: [...]` option or the `extension(...)` method: | ||
|
||
```ruby | ||
field :name, String, null: false, extensions: [UpcaseExtension] | ||
# or: | ||
field :description, String, null: false do | ||
extension(UpcaseExtension) | ||
end | ||
``` | ||
|
||
See below for how extensions may modify fields. | ||
|
||
### Modifying field configuration | ||
|
||
When extensions are attached, they are initialized with a `field:` and `options:`. During `#initialize`, they may extend the field they're attached to. For example: | ||
|
||
```ruby | ||
class SearchableExtension < GraphQL::Schema::FieldExtension | ||
def initialize(field:, options:) | ||
# add an argument to this field: | ||
field.argument(:query, String, required: false, description: "A search query") | ||
# and always call super: | ||
super | ||
end | ||
end | ||
``` | ||
|
||
This way, an extension can encapsulate a behavior requiring several configuration options. | ||
|
||
### Modifying field execution | ||
|
||
Extensions have two hooks that wrap field resolution. Since GraphQL-Ruby supports deferred execution, these hooks _might not_ be called back-to-back. | ||
|
||
First, {{ "GraphQL::Schema::FieldExtension#before_resolve" | api_doc }} is called. `before_resolve` should `yield(object, arguments)` to continue execution. If it doesn't `yield`, then the field won't resolve, and the methods return value will be returned to GraphQL instead. | ||
|
||
After resolution, {{ "GraphQL::Schema::FieldExtension#after_resolve" | api_doc }} is called. Whatever that method returns will be used as the field's return value. | ||
|
||
See the linked API docs for the parameters of those methods. | ||
|
||
#### Execution "memo" | ||
|
||
One parameter to `after_resolve` deserves special attention: `memo:`. `before_resolve` _may_ yield a third value. For example: | ||
|
||
```ruby | ||
def before_resolve(object:, arguments:, **rest) | ||
# yield the current time as `memo` | ||
yield(object, arguments, Time.now.to_i) | ||
end | ||
``` | ||
|
||
If a third value is yielded, it will be passed to `after_resolve` as `memo:`, for example: | ||
|
||
```ruby | ||
def after_resolve(value:, memo:, **rest) | ||
puts "Elapsed: #{Time.now.to_i - memo}" | ||
# Return the original value | ||
value | ||
end | ||
``` | ||
|
||
This allows the `before_resolve` hook to pass data to `after_resolve`. | ||
|
||
Instance variables may not be used because, in a given GraphQL query, the same field may be resolved several times concurrently, and that would result in overriding the instance variable in an unpredictable way. (In fact, extensions are frozen to prevent instance variable writes.) | ||
|
||
### Extension options | ||
|
||
The `extension(...)` method takes an optional second argument, for example: | ||
|
||
```ruby | ||
extension(LimitExtension, limit: 20) | ||
``` | ||
|
||
In this case, `{limit: 20}` will be passed as `options:` to `#initialize` and `options[:limit]` will be `20`. | ||
|
||
For example, options can be used for modifying execution: | ||
|
||
```ruby | ||
def after_resolve(value:, **rest) | ||
# Apply the limit from the options | ||
value.limit(options[:limit]) | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, thanks!