Skip to content

Commit 5063ab7

Browse files
committed
Document added_in and removed_in
1 parent ea70c48 commit 5063ab7

File tree

2 files changed

+146
-101
lines changed

2 files changed

+146
-101
lines changed

guides/changesets/definition.md

Lines changed: 142 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,74 @@ desc: Creating a set of modifications to release in an API version
99
index: 2
1010
---
1111

12-
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.
12+
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.
1313

14-
For example, this Changeset marks the old `Recipe.flag` field as deprecated:
14+
__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).
15+
16+
17+
## Changeset Classes
18+
19+
This Changeset will be available to any client whose `context[:changeset_version]` is on or after `2020-12-01`:
1520

1621
```ruby
1722
# app/graphql/changesets/deprecate_recipe_flag.rb
18-
class Changesets::DeprecateRecipeFlag < GraphQL::Enterprise::Changeset
23+
class Changesets::DeprecateRecipeTags < GraphQL::Enterprise::Changeset
1924
release "2020-12-01"
20-
modifies Types::Recipe do
21-
field :flag, Types::RecipeFlag, null: false, deprecation_reason: "Recipes now have multiple flags, use `flags` instead."
22-
end
2325
end
2426
```
2527

26-
Then this Changeset removes `Recipe.flag` entirely:
28+
Additionally, Changesets must be {% internal_link "released", "/changesets/releases" %} for their changes to be published.
29+
30+
## Publishing with `added_in:`
31+
32+
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:
2733

2834
```ruby
29-
# app/graphql/changesets/remove_recipe_flag.rb
30-
class Changesets::RemoveRecipeFlag < GraphQL::Enterprise::Changeset
31-
release "2021-03-01"
32-
modifies Types::Recipe do
33-
remove_field :flag
34-
end
35+
field :search_recipes, [Types::Recipe] do
36+
argument :query, String
37+
argument :tags, [Types::RecipeTag], required: false, added_in: Changesets::AddRecipeTags
3538
end
3639
```
3740

38-
Additionally, the Changesets must be added to the schema (see the {% internal_link "Releases guide", "/changesets/releases" %}):
41+
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:
3942

4043
```ruby
41-
class MyAppSchema < GraphQL::Schema
42-
# ...
43-
use GraphQL::Enterprise::Changeset::Release, changesets: [
44-
Changesets::DeprecateRecipeFlag,
45-
Changesets::RemoveRecipeFlag,
46-
]
44+
field :rating, Integer, "A 1-5 score for this recipe" # This definition will be superseded by the following one
45+
field :rating, Float, "A 1.0-5.0 score for this recipe", added_in: Changesets::FloatingPointRatings
46+
```
47+
48+
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.)
49+
50+
## Removing with `removed_in:`
51+
52+
A `removed_in:` configuration removes something in the named changeset. For example, these enum values are replaced with more clearly-named ones:
53+
54+
```ruby
55+
class Types::RecipeTag < Types::BaseEnum
56+
# These are replaced by *_HEAT below:
57+
value :SPICY, removed_in: Changesets::ClarifyHeatTags
58+
value :MEDIUM, removed_in: Changesets::ClarifyHeatTags
59+
value :MILD, removed_in: Changesets::ClarifyHeatTags
60+
# These new tags are more clear:
61+
value :SPICY_HEAT, added_in: Changesets::ClarifyHeatTags
62+
value :MEDIUM_HEAT, added_in: Changesets::ClarifyHeatTags
63+
value :MILD_HEAT, added_in: Changesets::ClarifyHeatTags
64+
end
65+
```
66+
67+
If something has been defined several times, a `removed_in:` configuration removes _all_ definitions:
68+
69+
```ruby
70+
class Mutations::SubmitRecipeRating < Mutations::BaseMutation
71+
# This is replaced in future API versions by the following argument
72+
argument :rating, Integer
73+
# This replaces the previous, but in another future version,
74+
# it is removed completely (and so is the previous one)
75+
argument :rating, Float, added_in: Changesets::FloatingPointRatings, removed_in: Changesets::RemoveRatingsCompletely
4776
end
4877
```
4978

50-
Although the changesets above have one modification each, a changeset may have any number of modifications in it.
79+
## Examples
5180

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

@@ -59,114 +88,126 @@ See below for the different kind of modifications you can make in a changeset:
5988
- [Types](#types): changing one type definition for another
6089
- [Runtime](#runtime): choosing a behavior at runtime based on the current request and changeset
6190

62-
## Fields
91+
### Fields
6392

64-
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:
93+
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.
6594

6695
```ruby
67-
class Changesets::RecipeMigration < GraphQL::Enterprise::Changeset
68-
modifies Types::Recipe do
69-
# modify `Recipe`'s fields here
70-
end
96+
class Types::Recipe < Types::BaseObject
97+
# This new field is available when `context[:changeset_version]`
98+
# is on or after the release date of `AddRecipeTags`
99+
field :tags, [Types::RecipeTag], added_in: Changeset::AddRecipeTags
71100
end
72101
```
73102

74-
Then...
103+
To remove a field, add a `removed_in: ...` configuration to the last definition of the field:
75104

76-
- 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.
77-
- To remove a field, use `remove_field(field_name)`, where `field_name` is the name given to `field(...)` (usually an underscore-cased symbol)
105+
```ruby
106+
class Types::Recipe < Types::BaseObject
107+
# Even after migrating to floating point values,
108+
# the "rating" feature never took off,
109+
# so we removed it entirely eventually.
110+
field :rating, Integer
111+
field :rating, Float, added_in: Changeset::FloatingPointRatings,
112+
removed_in: Changeset::RemoveRatings
113+
end
114+
```
78115

79116
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.
80117

81-
## Arguments
118+
### Arguments
82119

83-
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:
120+
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:
84121

85122
```ruby
86-
class Changesets::FilterMigration < GraphQL::Enterprise::Changeset
87-
modifies Types::IngredientsFilter do
88-
# modify input object arguments here
89-
end
90-
# ...
123+
class Types::RecipesFilter < Types::BaseInputObject
124+
argument :rating, Integer
125+
# This new definition is available when
126+
# the client's `context[:changeset_version]` includes `FloatingPointRatings`
127+
argument :rating, Float, added_in: Changesets::FloatingPointRatings
128+
end
91129
```
92130

93-
When versioning field arguments, use a second `modifies(field_name) { ... }` call to select the field to modify:
131+
To remove an argument entirely, add a `removed_in: ...` configuration to the last definition. It will remove _all_ implementations for that argument. For example:
94132

95133
```ruby
96-
# ...
97-
modifies Types::Query do
98-
modifies :ingredients do
99-
# modify the arguments of `Query.ingredients(...)` here
100-
end
101-
end
134+
class Mutations::SubmitRating < Mutations::BaseMutation
135+
# Remove this because it's irrelevant:
136+
argument :phone_number, String, removed_in: Changesets::StopCollectingPersonalInformation
102137
end
103138
```
104139

105-
Then...
106-
107-
- 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.
108-
- To remove an argument, use `remove_argument(argument_name)`, where `argument_name` is the name given to `field(...)` (usually an underscore-cased symbol)
109-
110140
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.
111141

112-
## Enum Values
142+
### Enum Values
113143

114-
In a Changeset, you can add, redefine, or remove enum values. First, use `modifies ... do ... end`, naming the enum type:
144+
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:
115145

116146
```ruby
117-
class Changesets::RecipeFlagMigration < GraphQL::Enterprise::Changeset
118-
modifies Types::RecipeFlag do
119-
# Modify `RecipeFlag`'s values here
120-
end
147+
class Types::RecipeTag < Types::BaseEnum
148+
# This enum will accept and return `KETO` only when the client's API version
149+
# includes `AddKetoDietSupport`'s release date.
150+
value :KETO, added_in: Changesets::AddKetoDietSupport
121151
end
122152
```
123153

124-
Then...
154+
Values can be removed with `removed_in:`, for example:
125155

126-
- 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.
127-
- To remove a value, use `remove_value(name)`, where `name` is the name given to `value(...)` (an all-caps string)
156+
```ruby
157+
class Types::RecipeTag < Types::BaseEnum
158+
# Old API versions will serve this value;
159+
# new versions won't accept it or return it.
160+
value :GRAPEFRUIT_DIET, removed_in: Changesets::RemoveLegacyDiets
161+
end
162+
```
128163

129164
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.
130165

131-
## Unions
166+
### Unions
132167

133-
In a Changeset, you can add to or remove from a union's possible types. First, use `modifies ...`, naming the union type:
168+
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:
134169

135170
```ruby
136-
class Changesets::MigrateLegacyCookingTechniques < GraphQL::Enterprise::Changeset
137-
modifies Types::CookingTechnique do
138-
# change the possible_types of the `CookingTechnique` union here
139-
end
140-
end
171+
class Types::Cookable < Types::BaseUnion
172+
possible_types Types::Recipe, Types::Ingredient
173+
# Add this to the union when clients opt in to our new feature:
174+
possible_types Types::Cuisine, added_in: Changeset::ReleaseCuisines
141175
```
142176

143-
Then...
177+
To remove a member from a union, move it to a `possible_types` call with `removed_in: ...`:
144178

145-
- 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.
146-
- To remove one or more more possible types, use `remove_possible_types(*object_types)`, passing one or more object type classes
179+
```ruby
180+
# Stop including this in the union in new API versions:
181+
possible_types Types::Chef, removed_in: Changeset::LessChefHype
182+
```
147183

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

150-
## Interfaces
186+
### Interfaces
151187

152-
In a Changeset, you can add to or remove from an object type's interface definitions. First, use `modifies ...`, naming the object type:
188+
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:
153189

154190
```ruby
155-
class Changesets::ModifyImplements < GraphQL::Enterprise::Changeset
156-
modifies Types::Ingredient do
157-
# change `Ingredient`'s interface implementations here
158-
end
191+
class Types::Recipe < Types::BaseObject
192+
# Add this new implementation in new API versions only:
193+
implements Types::RssSubject, added_in: Changesets::AddRssSupport
159194
end
160195
```
161196

162-
Then...
163197

164-
- 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.
165-
- To remove one or more more interface implementations, use `remove_implements(*interface_types)`, passing one or more interface type modules
198+
To remove one or more more interface implementations, add `removed_in:` to the `implements ...` configuration, for example:
199+
200+
```ruby
201+
implements Types::RssSubject,
202+
added_in: Changesets::AddRssSupport,
203+
# Sadly, nobody seems to want to use this,
204+
# so we removed it all:
205+
removed_in: Changesets::RemoveRssSupport
206+
```
166207

167208
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.)
168209

169-
## Types
210+
### Types
170211

171212
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.)
172213

@@ -175,20 +216,20 @@ First, to define two types with the same name, make two different type definitio
175216
```ruby
176217
# app/graphql/types/legacy_recipe_flag.rb
177218

178-
# In the old version of the schema, "recipe flags" were limited to defined set of values.
179-
# This enum was renamed from `Types::RecipeFlag`, then `graphql_name("RecipeFlag")`
219+
# In the old version of the schema, "recipe tags" were limited to defined set of values.
220+
# This enum was renamed from `Types::RecipeTag`, then `graphql_name("RecipeTag")`
180221
# was added for GraphQL.
181-
class Types::LegacyRecipeFlag < Types::BaseEnum
182-
graphql_name "RecipeFlag"
222+
class Types::LegacyRecipeTag < Types::BaseEnum
223+
graphql_name "RecipeTag"
183224
# ...
184225
end
185226
```
186227

187228
```ruby
188229
# app/graphql/types/recipe_flag.rb
189230

190-
# But in the new schema, each flag is a full-fledge object with fields of its own
191-
class Types::RecipeFlag < Types::BaseObject
231+
# But in the new schema, each tag is a full-fledged object with fields of its own
232+
class Types::RecipeTag < Types::BaseObject
192233
field :name, String, null: false
193234
field :is_vegetarian, Boolean, null: false
194235
# ...
@@ -197,28 +238,34 @@ end
197238

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

200-
```ruby
201-
class Changesets::MigrateRecipeFlagToObject < GraphQL::Enterprise::Changeset
202-
modifies Types::Recipe do
203-
# in types/recipe.rb, this is defined with `field :flags, [Types::LegacyRecipeFlag]`
204-
# Here, update the field to use the _object_ instead:
205-
update_field :flags, [Types::RecipeFlag]
241+
```diff
242+
class Types::Recipe < Types::BaseObject
243+
244+
# Change this definition to point at the newly-renamed _legacy_ type
245+
# (It's the same type definition, but the Ruby class has a new name)
246+
- field :tags, [Types::RecipeTag]
247+
+ field :tags, [Types::LegacyRecipeTag]
248+
249+
# And add a new field for the new type:
250+
+ field :tags, [Types::RecipeTag], added_in: Changesets::MigrateRecipeTagToObject
206251
end
207-
end
208252
```
209253

210-
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.
254+
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.
211255

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

214258
```ruby
215259
class Types::Recipe < Types::BaseObject
216-
# Here's the original definition, which is modified by `MigrateRecipeFlagToObject`:
217-
field :flags, [Types::LegacyRecipeFlag], null: false
260+
# Here's the original definition which returns enum values:
261+
field :tags, [Types::LegacyRecipeTag], null: false
262+
# Here's the new definition which replaces the previous one on new API versions:
263+
field :tags, [Types::RecipeTag], null: false, added_in: Changesets::MigrateRecipeTagToObject
218264

219265
def flags
220266
all_flag_objects = object.flag_objects
221-
if Changesets::MigrateRecipeFlagToObject.active?(context)
267+
if Changesets::MigrateRecipeTagToObject.active?(context)
268+
# Here's the new behavior, returning full objects:
222269
all_flag_objects
223270
else
224271
# Convert this to enum values, for legacy behavior:

guides/changesets/overview.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Changesets are a _complementary_ evolution technique to continuous additions. In
2121
For example, if you add a values to an Enum, you can just add it to the existing schema:
2222

2323
```diff
24-
class Types::RecipeFlag < Types::BaseEnum
24+
class Types::RecipeTag < Types::BaseEnum
2525
value "LOW_FAT"
2626
value "LOW_CARB"
2727
+ value "VEGAN"
@@ -33,11 +33,9 @@ For example, if you add a values to an Enum, you can just add it to the existing
3333
However, if you want to change the schema in ways that would _break_ previous queries, you can do that with a Changeset:
3434

3535
```ruby
36-
class RemoveIrrelevantFlags < GraphQL::Enterprise::Changeset
37-
modifies Types::RecipeFlag do
38-
# Turns out this makes you sick:
39-
remove_value "GRAPEFRUIT_DIET"
40-
end
36+
class Types::RecipeTag < Types::BaseEnum
37+
# Turns out this makes you sick:
38+
value "GRAPEFRUIT_DIET", removed_in: Changesets::RemoveLegacyDiets
4139
end
4240
```
4341

0 commit comments

Comments
 (0)