File: definition.md

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (300 lines) | stat: -rw-r--r-- 12,330 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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
---
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` string. Once a Changeset class is defined, it can be referenced with `added_in: ...` or `removed_in: ...` configurations in the schema.

__Note:__ Before GraphQL-Enterprise 1.3.0, Changesets were configured with `modifies ...` blocks. These blocks are still supported and you can find the documentation for that API [on GitHub](https://github.com/rmosolgo/graphql-ruby/blob/v2.0.22/guides/changesets/definition.md).


## Changeset Classes

This Changeset will be available to any client whose `context[:changeset_version]` is on or after `2020-12-01`:

```ruby
# app/graphql/changesets/deprecate_recipe_flag.rb
class Changesets::DeprecateRecipeTags < GraphQL::Enterprise::Changeset
  release "2020-12-01"
end
```

Additionally, Changesets must be {% internal_link "released", "/changesets/releases" %} for their changes to be published.

## Publishing with `added_in:`

New things can be published in a changeset by adding `added_in: SomeChangeset` to their configuration. For example, to add a new argument to a field:

```ruby
field :search_recipes, [Types::Recipe] do
  argument :query, String
  argument :tags, [Types::RecipeTag], required: false, added_in: Changesets::AddRecipeTags
end
```

You can also provide a _replacement_ implementation by using `added_in:`. When a new definition has the same name as an existing definition, it implicitly replaces the previous definition in new versions of the API. For example:

```ruby
field :rating, Integer, "A 1-5 score for this recipe" # This definition will be superseded by the following one
field :rating, Float, "A 1.0-5.0 score for this recipe", added_in: Changesets::FloatingPointRatings
```

Here, a new implementation for `rating` will be used when clients requests an API version that includes `Changesets::FloatingPointRatings`. (If the client requests a version _before_ that changeset, then the preceding implementation would be used instead.)

## Removing with `removed_in:`

A `removed_in:` configuration removes something in the named changeset. For example, these enum values are replaced with more clearly-named ones:

```ruby
class Types::RecipeTag < Types::BaseEnum
  # These are replaced by *_HEAT below:
  value :SPICY, removed_in: Changesets::ClarifyHeatTags
  value :MEDIUM, removed_in: Changesets::ClarifyHeatTags
  value :MILD, removed_in: Changesets::ClarifyHeatTags
  # These new tags are more clear:
  value :SPICY_HEAT, added_in: Changesets::ClarifyHeatTags
  value :MEDIUM_HEAT, added_in: Changesets::ClarifyHeatTags
  value :MILD_HEAT, added_in: Changesets::ClarifyHeatTags
end
```

If something has been defined several times, a `removed_in:` configuration removes _all_ definitions:

```ruby
class Mutations::SubmitRecipeRating < Mutations::BaseMutation
  # This is replaced in future API versions by the following argument
  argument :rating, Integer
  # This replaces the previous, but in another future version,
  # it is removed completely (and so is the previous one)
  argument :rating, Float, added_in: Changesets::FloatingPointRatings, removed_in: Changesets::RemoveRatingsCompletely
end
```

## Examples

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

To add or redefine a field, use `field(..., added_in: ...)`, including all configuration values for the new implementation (see {{ "GraphQL::Schema::Field#initialize" | api_doc }}). The definition given here will override the previous definition (if there was one) whenever this Changeset applies.

```ruby
class Types::Recipe < Types::BaseObject
  # This new field is available when `context[:changeset_version]`
  # is on or after the release date of `AddRecipeTags`
  field :tags, [Types::RecipeTag], added_in: Changeset::AddRecipeTags
end
```

To remove a field, add a `removed_in: ...` configuration to the last definition of the field:

```ruby
class Types::Recipe < Types::BaseObject
  # Even after migrating to floating point values,
  # the "rating" feature never took off,
  # so we removed it entirely eventually.
  field :rating, Integer
  field :rating, Float, added_in: Changeset::FloatingPointRatings,
    removed_in: Changeset::RemoveRatings
end
```

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

You can add, redefine, or remove arguments that belong to fields, input objects, or resolvers. Use `added_in: ...` to provide a new (or updated) definition for an argument, for example:

```ruby
class Types::RecipesFilter < Types::BaseInputObject
  argument :rating, Integer
  # This new definition is available when
  # the client's `context[:changeset_version]` includes `FloatingPointRatings`
  argument :rating, Float, added_in: Changesets::FloatingPointRatings
end
```

To remove an argument entirely, add a `removed_in: ...` configuration to the last definition. It will remove _all_ implementations for that argument. For example:

```ruby
class Mutations::SubmitRating < Mutations::BaseMutation
  # Remove this because it's irrelevant:
  argument :phone_number, String, removed_in: Changesets::StopCollectingPersonalInformation
end
```

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

With Changesets, you can add, redefine, or remove enum values. To add a new value (or provide a new implementation for a value), include `added_in:` in the `value(...)` configuration:

```ruby
class Types::RecipeTag < Types::BaseEnum
  # This enum will accept and return `KETO` only when the client's API version
  # includes `AddKetoDietSupport`'s release date.
  value :KETO, added_in: Changesets::AddKetoDietSupport
end
```

Values can be removed with `removed_in:`, for example:

```ruby
class Types::RecipeTag < Types::BaseEnum
  # Old API versions will serve this value;
  # new versions won't accept it or return it.
  value :GRAPEFRUIT_DIET, removed_in: Changesets::RemoveLegacyDiets
end
```

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

You can add to or remove from a union's possible types. To release a new union member, include `added_in:` in the `possible_types` configuration:

```ruby
class Types::Cookable < Types::BaseUnion
 possible_types Types::Recipe, Types::Ingredient
 # Add this to the union when clients opt in to our new feature:
 possible_types Types::Cuisine, added_in: Changeset::ReleaseCuisines
```

To remove a member from a union, move it to a `possible_types` call with `removed_in: ...`:

```ruby
# Stop including this in the union in new API versions:
possible_types Types::Chef, removed_in: Changeset::LessChefHype
```

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

### Interfaces

You can add to or remove from an object type's interface definitions. To add one or more interface implementations, use `implements(..., added_in:)`. This will add the interface and its fields to the object whenever this Changeset is active, for example:

```ruby
class Types::Recipe < Types::BaseObject
  # Add this new implementation in new API versions only:
  implements Types::RssSubject, added_in: Changesets::AddRssSupport
end
```


To remove one or more more interface implementations, add `removed_in:` to the `implements ...` configuration, for example:

```ruby
  implements Types::RssSubject,
    added_in: Changesets::AddRssSupport,
    # Sadly, nobody seems to want to use this,
    # so we removed it all:
    removed_in: Changesets::RemoveRssSupport
```

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 tags" were limited to defined set of values.
# This enum was renamed from `Types::RecipeTag`, then `graphql_name("RecipeTag")`
# was added for GraphQL.
class Types::LegacyRecipeTag < Types::BaseEnum
  graphql_name "RecipeTag"
  # ...
end
```

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

# But in the new schema, each tag is a full-fledged object with fields of its own
class Types::RecipeTag < 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:

```diff
  class Types::Recipe < Types::BaseObject

# Change this definition to point at the newly-renamed _legacy_ type
# (It's the same type definition, but the Ruby class has a new name)
-   field :tags, [Types::RecipeTag]
+   field :tags, [Types::LegacyRecipeTag]

# And add a new field for the new type:
+   field :tags, [Types::RecipeTag], added_in: Changesets::MigrateRecipeTagToObject
  end
```

With that Changeset, `Recipe.tags` 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 returns enum values:
  field :tags, [Types::LegacyRecipeTag], null: false
  # Here's the new definition which replaces the previous one on new API versions:
  field :tags, [Types::RecipeTag], null: false, added_in: Changesets::MigrateRecipeTagToObject

  def flags
    all_flag_objects = object.flag_objects
    if Changesets::MigrateRecipeTagToObject.active?(context)
      # Here's the new behavior, returning full objects:
      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" %}.