File: definition.md

package info (click to toggle)
ruby-graphql 1.13.15-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 8,904 kB
  • sloc: ruby: 69,655; yacc: 444; javascript: 330; makefile: 6
file content (253 lines) | stat: -rw-r--r-- 10,119 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
---
layout: guide
doc_stub: false
search: true
enterprise: true
section: GraphQL Enterprise - Changesets
title: Defining Changesets
desc: Creating a set of modifications to release in an API version
index: 2
---

After {% internal_link "installing Changeset integrations", "/changesets/installation" %} in your schema, you can create Changesets which modify parts of the schema. Changesets extend `GraphQL::Enterprise::Changeset` and include a `release` and some `modifies ...` configurations.

For example, this Changeset marks the old `Recipe.flag` field as deprecated:

```ruby
# app/graphql/changesets/deprecate_recipe_flag.rb
class Changesets::DeprecateRecipeFlag < GraphQL::Enterprise::Changeset
  release "2020-12-01"
  modifies Types::Recipe do
    field :flag, Types::RecipeFlag, null: false, deprecation_reason: "Recipes now have multiple flags, use `flags` instead."
  end
end
```

  Then this Changeset removes `Recipe.flag` entirely:

```ruby
# app/graphql/changesets/remove_recipe_flag.rb
class Changesets::RemoveRecipeFlag < GraphQL::Enterprise::Changeset
  release "2021-03-01"
  modifies Types::Recipe do
    remove_field :flag
  end
end
```

Additionally, the Changesets must be added to the schema (see the {% internal_link "Releases guide", "/changesets/releases" %}):

```ruby
class MyAppSchema < GraphQL::Schema
  # ...
  use GraphQL::Enterprise::Changesets::Release, changesets: [
    Changesets::DeprecateRecipeFlag,
    Changesets::RemoveRecipeFlag,
  ]
end
```

Although the changesets above have one modification each, a changeset may have any number of modifications in it.

See below for the different kind of modifications you can make in a changeset:

- [Fields](#fields): adding, modifying, and removing fields
- [Arguments](#arguments): adding, modifying, and removing arguments
- [Enum values](#enum-values): adding, modifying, and removing arguments
- [Unions](#unions): adding or removing object types from a union
- [Interfaces](#interfaces): adding or removing interface implementations from object types
- [Types](#types): changing one type definition for another
- [Runtime](#runtime): choosing a behavior at runtime based on the current request and changeset

### Fields

In a Changeset, you can add, redefine, or remove fields that belong to object types, interface types, or resolvers. First, use `modifies ... do ... end`, naming the owner of the field:

```ruby
class Changesets::RecipeMigration < GraphQL::Enterprise::Changeset
  modifies Types::Recipe do
    # modify `Recipe`'s fields here
  end
end
```

Then...

- To add or redefine a field, use `field(...)`, including the same configurations you'd use in a type definition (see {{ "GraphQL::Schema::Field#initialize" | api_doc }}). The definition given here will override the previous definition (if there was one) whenever this Changeset applies.
- To remove a field, use `remove_field(field_name)`, where `field_name` is the name given to `field(...)` (usually an underscore-cased symbol)

When a field is removed, queries that request that field will be invalid, unless the client has requested a previous API version where the field is still available.

### Arguments

In a Changeset, you can add, redefine, or remove arguments that belong to fields, input objects, or resolvers. Use `modifies` to select the argument owner, for example:

```ruby
class Changesets::FilterMigration < GraphQL::Enterprise::Changeset
  modifies Types::IngredientsFilter do
    # modify input object arguments here
  end
  # ...
```

When versioning field arguments, use a second `modifies(field_name) { ... }` call to select the field to modify:

```ruby
  # ...
  modifies Types::Query do
    modifies :ingredients do
      # modify the arguments of `Query.ingredients(...)` here
    end
  end
end
```

Then...

- To add or redefine an argument, use `argument(...)`, passing the same configurations you'd usually pass to `argument(...)` (see {{ "GraphQL::Schema::Argument#initialize" | api_doc }}). The redefined argument will override any previous definitions whenever this Changeset is active.
- To remove an argument, use `remove_argument(argument_name)`, where `argument_name` is the name given to `field(...)` (usually an underscore-cased symbol)

When arguments are removed, the schema will reject any queries which use them unless the client has requested a previous API version where the argument is still allowed.

### Enum Values

In a Changeset, you can add, redefine, or remove enum values. First, use `modifies ... do ... end`, naming the enum type:

```ruby
class Changesets::RecipeFlagMigration < GraphQL::Enterprise::Changeset
  modifies Types::RecipeFlag do
    # Modify `RecipeFlag`'s values here
  end
end
```

Then...

- To add a value, use `value(...)`, passing the same configurations you'd usually pass to `value(...)` in an enum type (see {{ "GraphQL::Schema::Enum.value" | api_doc }}). The configuration given here will override previous configurations whenever this Changeset applies.
- To remove a value, use `remove_value(name)`, where `name` is the name given to `value(...)` (an all-caps string)

When enum values are removed, they won't be accepted as input and they won't be allowed as return values from fields unless the client has requested a previous API version where those values are still allowed.

### Unions

In a Changeset, you can add to or remove from a union's possible types. First, use `modifies ...`, naming the union type:

```ruby
class Changesets::MigrateLegacyCookingTechniques < GraphQL::Enterprise::Changeset
  modifies Types::CookingTechnique do
    # change the possible_types of the `CookingTechnique` union here
  end
end
```

Then...

- To add one or more possible types, use `possible_types(*object_types)`, passing one or more object type classes. The given types will be _added_ to the union's set of possible types whenever this Changeset is active.
- To remove one or more more possible types, use `remove_possible_types(*object_types)`, passing one or more object type classes

When a possible type is removed, it will not be associated with the union type in introspection queries or schema dumps.

### Interfaces

In a Changeset, you can add to or remove from an object type's interface definitions. First, use `modifies ...`, naming the object type:

```ruby
class Changesets::ModifyImplements < GraphQL::Enterprise::Changeset
  modifies Types::Ingredient do
    # change `Ingredient`'s interface implementations here
  end
end
```

Then...

- To add one or more interface implementations, use `implements(*interface_types)`, passing one or more interface type modules. This will add the interface and its fields to the object whenever this Changeset is active.
- To remove one or more more interface implementations, use `remove_implements(*interface_types)`, passing one or more interface type modules

When an interface implementation is removed, then the interface will not be associated with the object in introspection queries or schema dumps. Also, any fields inherited from the interface will be hidden from clients. (If the object defines the field itself, it will still be visible.)

### Types

Using Changesets, it's possible to define a new type using the same name as an old type. (Only one type per name is allowed for each query, but different queries can use different types for the same name.)

First, to define two types with the same name, make two different type definitions. One of them will have to use `graphql_name(...)` to specify the conflicting type name. For example, to migrate an enum type to an object type, define two types:

```ruby
# app/graphql/types/legacy_recipe_flag.rb

# In the old version of the schema, "recipe flags" were limited to defined set of values.
# This enum was renamed from `Types::RecipeFlag`, then `graphql_name("RecipeFlag")`
# was added for GraphQL.
class Types::LegacyRecipeFlag < Types::BaseEnum
  graphql_name "RecipeFlag"
  # ...
end
```

```ruby
# app/graphql/types/recipe_flag.rb

# But in the new schema, each flag is a full-fledge object with fields of its own
class Types::RecipeFlag < Types::BaseObject
  field :name, String, null: false
  field :is_vegetarian, Boolean, null: false
  # ...
end
```

Then, add or update fields or arguments to use the _new_ type instead of the old one. For example:

```ruby
class Changesets::MigrateRecipeFlagToObject < GraphQL::Enterprise::Changeset
  modifies Types::Recipe do
    # in types/recipe.rb, this is defined with `field :flags, [Types::LegacyRecipeFlag]`
    # Here, update the field to use the _object_ instead:
    update_field :flags, [Types::RecipeFlag]
  end
end
```

With that Changeset, `Recipe.flags` will return an object type instead of an enum type. Clients requesting older versions will still receive enum values from that field.

The resolver will probably need an update, too, for example:

```ruby
class Types::Recipe < Types::BaseObject
  # Here's the original definition, which is modified by `MigrateRecipeFlagToObject`:
  field :flags, [Types::LegacyRecipeFlag], null: false

  def flags
    all_flag_objects = object.flag_objects
    if Changesets::MigrateRecipeFlagToObject.active?(context)
      all_flag_objects
    else
      # Convert this to enum values, for legacy behavior:
      all_flag_objects.map { |f| f.name.upcase }
    end
  end
end
```

That way, legacy clients will continue to receive enum values while new clients will receive objects.

### Runtime

While a query is running, you can check if a changeset applies by using its `.active?(context)` method. For example:

```ruby
class Types::Recipe
  field :flag, Types::RecipeFlag, null: true

  def flag
    # Check if this changeset applies to the current request:
    if Changesets::DeprecateRecipeFlag.active?(context)
      Stats.count(:deprecated_recipe_flag, context[:viewer])
    end
    # ...
  end
end
```

Besides observability, you can use a runtime check when a resolver needs to pick a different behavior depending on the API version.

After defining a changeset, add it to the schema to {% internal_link "release it", "/changesets/releases" %}.