graph_weaver 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -4
  3. data/README.md +40 -88
  4. data/docs/alternatives.md +1 -7
  5. data/docs/cassettes.md +54 -59
  6. data/docs/editors.md +32 -47
  7. data/docs/errors.md +261 -369
  8. data/docs/federation.md +650 -837
  9. data/docs/generated_modules.md +380 -463
  10. data/docs/getting_started.md +211 -428
  11. data/docs/i18n.md +114 -177
  12. data/docs/logging.md +127 -116
  13. data/docs/real_world.md +26 -39
  14. data/docs/scalars.md +277 -310
  15. data/docs/testing.md +343 -486
  16. data/docs/transports.md +203 -268
  17. data/docs/upgrading.md +211 -560
  18. data/examples/README.md +38 -0
  19. data/examples/countries.rb +39 -0
  20. data/examples/federation.rb +62 -0
  21. data/examples/github/generate.rb +20 -0
  22. data/examples/github/generated/star_mutation.rb +126 -0
  23. data/examples/github/generated/stargazers_query.rb +232 -0
  24. data/examples/github/generated/starred_query.rb +151 -0
  25. data/examples/github/queries/star.graphql +8 -0
  26. data/examples/github/queries/stargazers.graphql +22 -0
  27. data/examples/github/queries/starred.graphql +11 -0
  28. data/examples/github/run.rb +43 -0
  29. data/examples/github/setup.rb +18 -0
  30. data/examples/rick_and_morty.rb +57 -0
  31. data/graph_weaver.gemspec +12 -3
  32. data/lib/graph_weaver/client.rb +30 -1
  33. data/lib/graph_weaver/codegen/emit.rb +5 -11
  34. data/lib/graph_weaver/codegen.rb +23 -55
  35. data/lib/graph_weaver/context_seam.rb +54 -0
  36. data/lib/graph_weaver/errors.rb +23 -15
  37. data/lib/graph_weaver/federation.rb +11 -2
  38. data/lib/graph_weaver/graph.rb +39 -29
  39. data/lib/graph_weaver/in_process.rb +15 -9
  40. data/lib/graph_weaver/internal/endpoint.rb +7 -5
  41. data/lib/graph_weaver/internal/headers.rb +19 -0
  42. data/lib/graph_weaver/internal/test_clients.rb +7 -11
  43. data/lib/graph_weaver/internal.rb +81 -13
  44. data/lib/graph_weaver/log_subscriber.rb +10 -2
  45. data/lib/graph_weaver/logging.rb +33 -13
  46. data/lib/graph_weaver/query_module.rb +44 -23
  47. data/lib/graph_weaver/retry.rb +12 -8
  48. data/lib/graph_weaver/rspec.rb +13 -24
  49. data/lib/graph_weaver/schema_loader.rb +52 -14
  50. data/lib/graph_weaver/tasks.rb +10 -2
  51. data/lib/graph_weaver/testing/cassette.rb +28 -5
  52. data/lib/graph_weaver/testing/endpoint.rb +14 -13
  53. data/lib/graph_weaver/testing/fake_client.rb +33 -3
  54. data/lib/graph_weaver/testing/router.rb +7 -3
  55. data/lib/graph_weaver/testing.rb +12 -4
  56. data/lib/graph_weaver/transport/http.rb +2 -2
  57. data/lib/graph_weaver/transport.rb +47 -23
  58. data/lib/graph_weaver/version.rb +1 -1
  59. data/lib/graph_weaver.rb +32 -10
  60. metadata +16 -3
  61. data/CHANGELOG.md +0 -3801
data/docs/upgrading.md CHANGED
@@ -1,34 +1,74 @@
1
1
  # Upgrading
2
2
 
3
- [Regenerate](#regenerate-on-every-upgrade) whichever version you're on, then
4
- read the one section that is yours: from [0.6.1](#upgrading-from-061), from
5
- [0.5.1](#upgrading-from-051), or [to 0.5.0](#upgrading-to-050) from anything
6
- older.
3
+ [Regenerate](#regenerate-on-every-upgrade) whichever version you're on, then read
4
+ the one section that is yours: from [0.7.1](#upgrading-from-071), from
5
+ [0.7.0](#upgrading-from-070) or from [0.6.1](#upgrading-from-061). Coming from
6
+ 0.6.0 or older, the path is that version's own upgrade notes — read them at the
7
+ tag they shipped under (`git show v0.7.1:docs/upgrading.md`), then this page from
8
+ 0.6.1 down.
7
9
 
8
10
  ## Regenerate on every upgrade
9
11
 
10
- **Any release can change what codegen emits.** Patch releases included — most of
12
+ **Any release can change what codegen emits**, patch releases included — most of
11
13
  them are fixes to a generated type, and a fix to a type is a change to the bytes.
12
- 0.5.1 was a patch and moved three of them.
13
-
14
14
  So `rake graph_weaver:generate` is part of upgrading the gem, every time, and
15
15
  `rake graph_weaver:verify` is the detector: it fails when the checked-in Ruby
16
- isn't what this version would write. Nothing beyond that is promised — there is
17
- no "generated output is stable within a minor" rule to lean on. What each release
18
- changed, and whether it needs a regenerate, is in the changelog.
16
+ isn't what this version would write. Nothing beyond that is promised — there is no
17
+ "generated output is stable within a minor" rule to lean on, and what each release
18
+ changed is in the [changelog](../CHANGELOG.md).
19
19
 
20
20
  A generated file's header names the release that wrote it, so the first `verify`
21
- after an upgrade reports the tree as stale whether or not codegen actually
22
- moved. That's the reminder working, not a false alarm.
21
+ after an upgrade reports the tree as stale whether or not codegen actually moved.
22
+ That's the reminder working, not a false alarm. Generation is deterministic, so
23
+ the diff is exactly what the new version emits differently and nothing else —
24
+ worth reading rather than rubber-stamping.
25
+
26
+ ## Upgrading from 0.7.1
27
+
28
+ A patch release, and one change with a shape: a generated module no longer
29
+ carries a client of its own — the graph it belongs to resolves one. Read the
30
+ left column and skip what isn't yours; the [changelog](../CHANGELOG.md) says why
31
+ each one moved.
32
+
33
+ | applies if you… | what changed |
34
+ |---|---|
35
+ | call `generate!`, `verify_generated!`, `Codegen.new` or `Codegen.generate` yourself — `grep -rn "client:" config lib Rakefile` | the `client:` kwarg is gone, and the call raises `unknown keyword: :client`. Say it once on the graph (`client` in a `GraphWeaver.graph` block), or as the app default (`GraphWeaver.client =`) for modules in no declared graph. `Graph#client` answers that object now, not the name of the constant holding it |
36
+ | assign a generated module's client — `grep -rn "\.client *=" app config lib` (a hit on `GraphWeaver.client =` is the app default, and still fine) | the writer is private, and `MyQuery.client = …` raises `NoMethodError`. A module's client comes from its graph, from `client:` on the call, or — for a module you parsed — from `GraphWeaver.parse(client:)`, which `client.parse` and `load_queries!` already pass |
37
+
38
+ Then regenerate, and the gate:
39
+
40
+ ```sh
41
+ # generated files no longer carry a DEFAULT_CLIENT
42
+ rake graph_weaver:generate
43
+
44
+ # red while any checked-in file is still what 0.7.1 wrote
45
+ rake graph_weaver:verify
46
+ ```
47
+
48
+ ## Upgrading from 0.7.0
23
49
 
24
- Generation is deterministic, so the diff is exactly what the new version emits
25
- differently and nothing else worth reading rather than rubber-stamping.
50
+ A patch release of fixes, and a typical app ticks none of these. Read the left
51
+ column and skip what isn't yours; the [changelog](../CHANGELOG.md) says why each
52
+ one moved.
53
+
54
+ | applies if you… | what changed |
55
+ |---|---|
56
+ | read `payload[:code]` in an alert or a dashboard expecting an HTTP status | it is a GraphQL error code or nothing now — the number is on `:http_status`, where it already was |
57
+ | assert a fabricated `userErrors` is non-empty under `graphql: :fake` | a list field whose name ends in `errors` fabricates `[]` — pin it (`{ "userErrors" => [{ "message" => "…" }] }`) to fabricate failures |
58
+ | grep or parse the debug log for `[req 3 …]` | the tag names the process: `[req 4123-3 …]` |
59
+ | run more than one graph with `cache: true` | the second graph caches to `schema-<url digest>.json` of its own instead of sharing the first's dump — `cache: "<path>"` names it yourself |
60
+ | commit your cassette directory | recording takes a `<cassette>.yml.lock` sidecar — gitignore `*.yml.lock` |
61
+ | `rescue ArgumentError` around the union-dispatch refusal | it is a `GraphWeaver::Error` naming the query file now |
62
+ | wrote a client of your own with a bare `attr_accessor :context` and mount it behind `Testing::Endpoint` | include `GraphWeaver::ContextSeam` — the lock lives on whoever owns the field |
63
+ | read `CHANGELOG.md` out of the installed gem | it isn't packaged any more; `changelog_uri` points at `blob/v<version>` |
64
+ | send a `File`, `IO`, `Pathname` or plain object as a variable under `graphql: :in_process` or `:fake` | refused there too now, as it already was over the wire — **a test that "proved" an upload works starts failing** |
65
+ | retry a 429/503 that arrives **with** a GraphQL errors body | a `Retry-After` header wins over the configured backoff now, as it already did for a raised `ServerError` — **nothing raises** |
26
66
 
27
67
  ## Upgrading from 0.6.1
28
68
 
29
- Mostly mechanical. Everything that wants your hands, or changes under you, is
30
- one row below; read the left column and skip what isn't yours. A typical app
31
- ticks two or three.
69
+ Mostly mechanical. Everything that wants your hands, or changes under you, is one
70
+ row below; read the left column and skip what isn't yours. A typical app ticks two
71
+ or three.
32
72
 
33
73
  | applies if you… | what changed |
34
74
  |---|---|
@@ -87,9 +127,9 @@ bundle exec rspec
87
127
  rake graph_weaver:verify
88
128
  ```
89
129
 
90
- **Two kinds of file answer that first grep, and only one needs your hands.**
91
- Hits under your generated directory (`app/graphql/generated/` by default) are the
92
- old names in machine-written code — step 3 rewrites them. Hits anywhere else are
130
+ **Two kinds of file answer that first grep, and only one needs your hands.** Hits
131
+ under your generated directory (`app/graphql/generated/` by default) are the old
132
+ names in machine-written code — step 3 rewrites them. Hits anywhere else are
93
133
  yours: `CastError` and `QueryValidationError`, renamed by hand.
94
134
 
95
135
  ### Renames
@@ -104,555 +144,166 @@ yours: `CastError` and `QueryValidationError`, renamed by hand.
104
144
 
105
145
  ### Behavior that changed under you
106
146
 
107
- The first one reaches every Rails app that never configured logging, and it is
108
- the only one here that shows up in production rather than in your code.
109
-
110
- - **A Rails app logs one line per GraphQL call, and emits one notification.**
111
- The railtie now sets `GraphWeaver.instrumenter` to the
112
- `ActiveSupport::Notifications` adapter and attaches
113
- `GraphWeaver::LogSubscriber`, so an app that configured neither gets
114
- `GraphWeaver billing/InvoicesQuery (12.3ms) ok` at **info** one line per
115
- operation, carrying nothing that can hold PII; the query and variables stay
116
- at debug. An instrumenter you set yourself is never replaced, and **to opt
117
- out, set `GraphWeaver.logger = nil` or `GraphWeaver.instrumenter = nil` in
118
- `config/initializers`** which now takes effect, so an app that worked
119
- around it with `config.after_initialize { GraphWeaver.logger = nil }` can
120
- drop that. In-process calls are in scope too: a bare schema class in a client
121
- slot (`GraphWeaver.client = MyApp::Schema`, `execute!(client: MyApp::Schema)`,
122
- a graph's `client "Billing::Schema"`) goes through the same wrapper
123
- `GraphWeaver.new(MyApp::Schema)` always used, so it produces events and log
124
- lines where it produced none. See [logging](logging.md).
125
- - **A `Retry` in front of a gateway starts actually retrying.** It read only
126
- the failures that *raised*, and Apollo Router answers everything it decides
127
- itself with a GraphQL errors body rate limiting is `503` plus
128
- `REQUEST_RATE_LIMITED`, its own faults are `500` plus a code — so the body
129
- won over the status and `retries: 3` made one attempt. A response retries now
130
- when its status is one a `ServerError` retries on (5xx, 408, 429), or when
131
- its error codes are named in `retry_codes:`. A `200` is never retried on
132
- status, and a mutation still gets one attempt unless `retry_mutations:
133
- true`. **This is real traffic you weren't sending** if the inert policy was
134
- what you wanted, `retries: 0`. `#throttled?` answers
135
- `REQUEST_RATE_LIMITED` too.
136
- - **A task that can't honour `SUPERGRAPH=` refuses instead of ignoring it.**
137
- The flag reaches the `federation:*` tasks and nothing else, so
138
- `SUPERGRAPH=public.graphql rake graph_weaver:queries:check` reported every
139
- query valid against a supergraph missing a field they select the wrong
140
- answer wearing a green tick. **A CI step that passes it to `generate`,
141
- `verify` or `queries:check` goes red**; drop the flag, or declare the
142
- supergraph on a graph so every run finds it.
143
- - **`InputError#field` names the input field, not the variable.** It is now
144
- `#path`'s last *named* segment — the slot that actually held the bad value,
145
- which is the one a form highlights where it used to be re-branded on the
146
- way out with the *variable* name. Nothing raises; the value just differs once
147
- a refusal happens inside an input object. **Read `error.path.first` wherever
148
- you wanted the variable**, and `#field` wherever you wanted the field. On a
149
- refusal that never got past the variable the two are the same, which is why
150
- this can pass unnoticed until the first nested input fails. An index is a
151
- position rather than a field, so it never becomes one: `execute(ids: [1, 2,
152
- "x"])` reports `#path` `["ids", 2]` and `#field` `"ids"`. **Self-check:**
153
- `grep -rn "\.field" app lib` every hit that indexes or compares an
154
- `InputError`'s `#field` is a place to decide which of the two you meant.
155
- - **The instrumentation payload's `:status` is a Symbol, and the HTTP status
156
- moved to `:http_status`.** `:status` is now `:ok`, `:errors` (the response
157
- came back carrying GraphQL errors) or `:failed` (it raised) — a 200 carrying
158
- errors is not a success, and only a symbol says that on both sides of the
159
- seam. Nothing raises: a subscriber comparing it to an Integer just stops
160
- matching. **A subscriber that branched on `payload[:status] == 200`, or on a
161
- 4xx/5xx, reads `:http_status` now** — which is nil in-process, where
162
- `:status` used to be a fabricated 200 so one subscriber could read both
163
- sides. The whole payload is a documented contract now; see
164
- [logging](logging.md#the-payload). **Self-check:** nothing subscribing to
165
- `execute.graph_weaver` means nothing to change — this reaches subscribers
166
- only.
167
- - **`respond_to?` on a result struct no longer answers true for a name that
168
- doesn't exist.** It used to say true for any near miss, which broke the
169
- standard duck-typing guard — `obj.pet if obj.respond_to?(:pet)` raised the
170
- very `NoMethodError` the hint exists to explain. **A branch that read the old
171
- answer now takes the other path**, and `struct.method(:nmae)` raises Ruby's
172
- bare `NameError` rather than a hinted one; `struct.nmae` still hints.
173
- - **A `graphql:` tag reaches a module generated with `client:`.** The baked
174
- client used to sit above the slot a tag swaps, so a bound module ran against
175
- its real endpoint under `graphql: :fake`. **If a spec relied on that**, it now
176
- runs against the fake — pass `client:` on the call, set `MyQuery.client =`, or
177
- tag the example `graphql: :live`.
147
+ The first one reaches every Rails app that never configured logging, and it is the
148
+ only one here that shows up in production rather than in your code.
149
+
150
+ - **A Rails app logs one line per GraphQL call, and emits one notification.** The
151
+ railtie sets `GraphWeaver.instrumenter` to the `ActiveSupport::Notifications`
152
+ adapter and attaches `GraphWeaver::LogSubscriber`, so an app that configured
153
+ neither gets `GraphWeaver billing/InvoicesQuery (12.3ms) ok` at **info** the
154
+ query and variables stay at debug. **To opt out, set `GraphWeaver.logger = nil`
155
+ or `GraphWeaver.instrumenter = nil` in `config/initializers`**, which now takes
156
+ effect (an app that worked around that with `config.after_initialize` can drop
157
+ it). In-process calls are in scope too: a bare schema class in a client slot goes
158
+ through the same wrapper, so it produces events and log lines where it produced
159
+ none. See [logging](logging.md).
160
+ - **A `Retry` in front of a gateway starts actually retrying.** It read only the
161
+ failures that *raised*, and Apollo Router answers everything it decides itself
162
+ with a GraphQL errors body, so `retries: 3` made one attempt. A response retries
163
+ now when its status is one a `ServerError` retries on (5xx, 408, 429), or when
164
+ its error codes are named in `retry_codes:`. **This is real traffic you weren't
165
+ sending** if the inert policy was what you wanted, `retries: 0`.
166
+ - **A task that can't honour `SUPERGRAPH=` refuses instead of ignoring it.** The
167
+ flag reaches the `federation:*` tasks and nothing else, so
168
+ `SUPERGRAPH=public.graphql rake graph_weaver:queries:check` used to report every
169
+ query valid against a supergraph missing fields they select. **A CI step that
170
+ passes it to `generate`, `verify` or `queries:check` goes red**; drop the flag,
171
+ or declare the supergraph on a graph.
172
+ - **`InputError#field` names the input field, not the variable.** It is `#path`'s
173
+ last *named* segment the slot that actually held the bad value — where it used
174
+ to be re-branded with the *variable* name. Nothing raises; the value just differs
175
+ once a refusal happens inside an input object. **Read `error.path.first` wherever
176
+ you wanted the variable.** An index is a position rather than a field, so
177
+ `execute(ids: [1, 2, "x"])` reports `#path` `["ids", 2]` and `#field` `"ids"`.
178
+ - **The instrumentation payload's `:status` is a Symbol, and the HTTP status moved
179
+ to `:http_status`.** `:status` is `:ok`, `:errors` or `:failed` a 200 carrying
180
+ errors is not a success, and only a symbol says that on both sides of the seam.
181
+ Nothing raises: a subscriber comparing it to an Integer just stops matching. **A
182
+ subscriber that branched on a 4xx/5xx reads `:http_status` now**, which is nil
183
+ in-process. The whole payload is a documented contract
184
+ [logging](logging.md#the-payload).
185
+ - **`respond_to?` on a result struct no longer answers true for a name that doesn't
186
+ exist.** It used to say true for any near miss, which broke the standard
187
+ duck-typing guard. **A branch that read the old answer now takes the other path**,
188
+ and `struct.method(:nmae)` raises Ruby's bare `NameError`; `struct.nmae` still
189
+ hints.
190
+ - **A `graphql:` tag reaches a module generated with `client:`.** The baked client
191
+ used to sit above the slot a tag swaps, so a bound module ran against its real
192
+ endpoint under `graphql: :fake`. **If a spec relied on that**, pass `client:` on
193
+ the call, or tag the example `graphql: :live`.
178
194
  - **`config.context`, `config.schema` and `config.router` are suite setup.**
179
195
  Setting any of the three once an example is running refuses, naming the
180
196
  per-example helper (`graphql_context`, `graphql_fake(schema:)`,
181
- `graphql_router(fake:)`). The tag builds an example's clients in a `before`
182
- hook of its own, which rspec runs ahead of any group `before`, so a set there
183
- was read too late and silently changed nothing a `config.context` that
184
- never reached a resolver, a `config.schema` the fake never saw. The refusal
185
- replaces a line that wasn't working. **Move it to an `around`, or to
186
- `GraphWeaver::Testing.configure` in the spec helper**; `configure` and
187
- `around` are unchanged.
188
- - **`result.to_json` is real JSON, and it is the wire shape.** It used to be
189
- Ruby's `Object#to_json` the `#inspect` string, quoted — so a log line or a
190
- cache write stored nothing, with no exception and no warning; under Rails
191
- `render json: result` instead shipped the *Ruby* prop names, trailing
192
- underscores included. Both now produce the response keys, each leaf back
193
- through its scalar registration's `serialize:`, so
194
- `Result.from_h(JSON.parse(result.to_json)) == result`. `#to_h` is unchanged
195
- and still the Ruby view. **Anything that parsed the old output, or diffed a
196
- cached copy of it, is reading something different now** — and `as_json` is
197
- emitted code, so a struct generated by 0.6.1 raises `GraphWeaver::Error`
198
- naming this until you regenerate.
199
- - **A schema dump introspected through a credentialed url still holds the
200
- token.** The provenance stamp wrote the transport's url verbatim, so a url
201
- carrying userinfo or an `?access_token=` landed in a file that gets
202
- committed. It records the endpoint bare now userinfo and any query
203
- parameter `filter_parameters` filters are droppedand re-introspection
204
- still authenticates from the dump's `auth_env`. **Run `rake
205
- graph_weaver:schema:refresh` once, and rotate the token if that file was ever
206
- pushed.**
207
- - **`verify` fails when the dump has fallen behind the schema class it was
208
- built from.** For an app that serves its own schema the dump is an artifact
209
- derived from code in the same repo, and everything downstream reads it, so
210
- `generate` and `verify` both called a tree up to date while the live
211
- resolvers had already moved. **A dump you deliberately keep behind your own
212
- schema is a red gate now** — `rake graph_weaver:schema:refresh`, or ask about
213
- no dump at all with `verify_generated!(schema:)`. It costs one in-process
214
- introspection per graph and never a network call.
197
+ `graphql_router(fake:)`). The tag builds an example's clients in a `before` hook
198
+ of its own, which rspec runs ahead of any group `before`, so a set there was read
199
+ too late and silently changed nothing. **Move it to an `around`, or to
200
+ `GraphWeaver::Testing.configure` in the spec helper.**
201
+ - **`result.to_json` is real JSON, and it is the wire shape.** It used to be Ruby's
202
+ `Object#to_json` the `#inspect` string, quoted — while under Rails
203
+ `render json: result` shipped the *Ruby* prop names. Both now produce the response
204
+ keys, each leaf back through its scalar registration's `serialize:`, so
205
+ `Result.from_h(JSON.parse(result.to_json)) == result`. `#to_h` is unchanged and
206
+ still the Ruby view. **Anything that parsed the old output is reading something
207
+ different now** — and `as_json` is emitted code, so a struct generated by 0.6.1
208
+ raises `GraphWeaver::Error` naming this until you regenerate.
209
+ - **A schema dump introspected through a credentialed url still holds the token.**
210
+ The provenance stamp wrote the transport's url verbatim. It records the endpoint
211
+ bare now userinfo and any query parameter `filter_parameters` filters are
212
+ dropped and re-introspection still authenticates from the dump's `auth_env`.
213
+ **Run `rake graph_weaver:schema:refresh` once, and rotate the token if that file
214
+ was ever pushed.**
215
+ - **`verify` fails when the dump has fallen behind the schema class it was built
216
+ from.** For an app that serves its own schema the dump is an artifact derived
217
+ from code in the same repo, so `generate` and `verify` both called a tree up to
218
+ date while the live resolvers had already moved. **A dump you deliberately keep
219
+ behind your own schema is a red gate now** `rake graph_weaver:schema:refresh`,
220
+ or ask about no dump at all with `verify_generated!(schema:)`. It costs one
221
+ in-process introspection per graph and never a network call.
215
222
  - **`@oneOf` starts being enforced if your dump is `.json`.** graphql-ruby's
216
- introspection query omits `isOneOf` unless asked, and its loader drops the
217
- field even when it is there, so every dump this gem has written said "not
218
- @oneOf" for every input object and the enforcing struct was never generated.
219
- **Regenerate (`rake graph_weaver:schema:refresh && rake
220
- graph_weaver:generate`) and the emitted `ONE_OF` starts refusing calls that
221
- set two fields** which your server was refusing all along, so the failure
222
- moves from the wire into `execute`. SDL dumps, inline SDL and a live class
223
- were always correct.
224
- - **A fake pin is told from an option by a schema lookup, not by casing.** The
225
- rule was "a dot or a leading capital is a pin", so a lowercase type could not
226
- be pinned at all: `graphql_fake("pokemon_v2_pokemon" => …)` against a Hasura
227
- API came back as `a fake doesn't take pokemon_v2_pokemon:`. Those pins work
228
- now. The other side of it: **a keyword that is a near-miss for a pin
229
- (`Persn: "Ada"`) raises `ArgumentError` from the fake** rather than
230
- `GraphWeaver::Error` from the override check the same key written in the
231
- leading positional hash is unchanged, and is the spelling for a schema whose
232
- vocabulary collides with an option name.
233
- - **Regenerate**, as ever — generated modules carry a private `GRAPH` naming the
234
- graph they were generated from, and a [multi-schema](getting_started.md#more-than-one-schema)
235
- app whose modules predate it refuses rather than guessing which schema a
236
- module belongs to. A generated `execute` also makes its request through the
237
- gem now (`from_response(dispatch(variables, client:))`), which is what lets
238
- an event name the graph; 0.6.1's modules keep working as they are, but `rake
239
- graph_weaver:verify` reports the tree out of date until you regenerate.
240
- Result structs also gained `==`/`eql?`/`hash`, `deconstruct_keys`, `#to_h`
241
- and `#as_json`, and the emitted guard in front of a `cast:` changed (below).
242
- - **Check that your `require "graph_weaver/rspec"` actually runs.** The old
243
- setup put it in `spec/support/graph_weaver.rb`, and rspec-rails ships the
244
- `spec/support` glob **commented out** — so if you never uncommented it, the
245
- tag did nothing and every `graphql: :fake` example has been hitting the real
246
- client. `rails g graph_weaver:install` now writes the line into
247
- `spec/rails_helper.rb` instead; **move yours there** if the glob isn't live.
248
- - **`graphql: :wire`, if you adopt it, needs webmock *enabled*** — `require
249
- "webmock/rspec"` in the spec helper. Having it in the Gemfile is not enough:
250
- `Bundler.require` loads webmock without installing its adapters, and the tag
251
- refuses before the first request rather than letting it leave the suite.
252
- - **A `DateTime` given for a `Date` variable is refused.** `DateTime` is a
253
- `Date` to Ruby, so it used to pass the cast untouched and go on the wire as
254
- `"2024-01-15T10:20:30+00:00"` where the schema said `ISO8601Date` — a lenient
255
- server truncated it, a strict one refused it. Truncating it here would be the
256
- same guess made silently, so it now raises an `InputError` naming the class
257
- and the fix: `$d of On: expected a Date, got a DateTime — pass .to_date if
258
- dropping the time of day is what you meant`. **Pass `.to_date` where a
259
- `DateTime` reaches a `Date` variable.** The pairings that already raised —
260
- a `Time` for a date, a `Date` for a timestamp — now raise that branded
261
- `InputError` rather than Ruby's *"no implicit conversion of Time into
223
+ introspection query omits `isOneOf` unless asked, so every dump this gem had
224
+ written said "not @oneOf" for every input object. **Regenerate and the emitted
225
+ `ONE_OF` starts refusing calls that set two fields** which your server was
226
+ refusing all along, so the failure moves from the wire into `execute`. SDL dumps,
227
+ inline SDL and a live class were always correct.
228
+ - **A fake pin is told from an option by a schema lookup, not by casing.** A
229
+ lowercase type could not be pinned at all (`graphql_fake("pokemon_v2_pokemon" =>
230
+ …)` against a Hasura API); those pins work now. The other side of it: **a keyword
231
+ that is a near-miss for a pin (`Persn: "Ada"`) raises `ArgumentError` from the
232
+ fake** rather than `GraphWeaver::Error` from the override check the same key
233
+ written in the leading positional hash is unchanged, and is the spelling for a
234
+ schema whose vocabulary collides with an option name.
235
+ - **A `DateTime` given for a `Date` variable is refused.** `DateTime` is a `Date`
236
+ to Ruby, so it used to pass the cast untouched and go on the wire as a full
237
+ timestamp where the schema said `ISO8601Date`. **Pass `.to_date`.** The pairings
238
+ that already raised a `Time` for a date, a `Date` for a timestamp — now raise a
239
+ branded `InputError` rather than Ruby's *"no implicit conversion of Time into
262
240
  String"*, and a `DateTime` or `Time.zone.now` for a *timestamp* converts
263
241
  losslessly where it used to raise.
264
- - **A `cast:` of your own gets the same guard and the same verdict.** A
265
- registration like `register_scalar("Date", Date, cast: :iso8601, serialize:
266
- :iso8601)` emitted a bare `value.is_a?(Date)` pass-through, so a `DateTime`
267
- went by untouched and your `serialize:` wrote a full timestamp into a date
268
- field — **pass `.to_date` there too**. Anything else wrong used to arrive as
269
- Ruby's own sentence about an argument you never wrote (`no implicit
270
- conversion of Integer into String`) under `kind: :unparseable`; the verdict
271
- is the library's now and splits the way Ruby does — a `TypeError` from a
272
- codec reads `expected a Date, got 5` under `kind: :type_mismatch`, an
273
- `ArgumentError` keeps the parser's words under `:unparseable`.
274
- **`#details[:type]` is the GraphQL type now, never a Ruby class** a
275
- `register_scalar("Money", BigDecimal)` field reads `"Money"`, not
276
- `"BigDecimal"`, and an input object reads its schema name rather than the
277
- class generated for it; the *message* still names the Ruby you may pass.
278
- **A spec matching the old
279
- message, or branching on `:unparseable` for a wrong class, needs updating**
280
- — and the guard is emitted into your generated files, so a checked-in one
281
- keeps the old behavior until you regenerate.
242
+ - **A `cast:` of your own gets the same guard and the same verdict.** A registration
243
+ like `register_scalar("Date", Date, cast: :iso8601, serialize: :iso8601)` emitted
244
+ a bare `value.is_a?(Date)` pass-through, so a `DateTime` went by untouched and
245
+ your `serialize:` wrote a full timestamp into a date field — **pass `.to_date`
246
+ there too**. Anything else wrong used to arrive as Ruby's own sentence under
247
+ `kind: :unparseable`; the verdict is the library's now and splits the way Ruby
248
+ does a `TypeError` from a codec reads `expected a Date, got 5` under
249
+ `:type_mismatch`, an `ArgumentError` keeps the parser's words under
250
+ `:unparseable`. **`#details[:type]` is the GraphQL type now, never a Ruby class.**
251
+ A spec matching the old message, or branching on `:unparseable` for a wrong class,
252
+ needs updating — and the guard is emitted into your generated files, so a
253
+ checked-in one keeps the old behavior until you regenerate.
282
254
  - **A field whose name a struct already answers to now generates as `name_`.**
283
- `class` becomes the prop `class_`, `hash` becomes `hash_`, and so on for
284
- `display`, `to_json`, `each` and (on an input) `supplied`. Nothing that used
285
- to work stops working: a key you aliased in the query to get past the old
286
- *"alias it in the query"* refusal still generates from that alias **drop
287
- the alias and regenerate** if you want the field's own name back. Only the
288
- Ruby name moves; the wire keeps the schema's spelling in both directions, so
289
- `result.class` is still Ruby's `class` and `result.class_` is the field. The
290
- prop is the field's one Ruby name, so `.coerce({ class_: })` and a result's
291
- `#to_h` and pattern matching all use it. An `InputError`'s structured half is
292
- the wire's throughout, so a refusal on that field reports `#path` `["class"]`
293
- and `#coordinate` `"Tricky.class"`. An **input** struct's `#to_h`
294
- is the wire hash it would send, `{"class" => …}`, and input structs don't
295
- pattern-match. Input types had no way past the old refusal at all, so a
296
- schema with a `class` column a Hasura `bool_exp` has one input field per
297
- column generates for the first time. The names that take an underscore are
298
- a list the gem owns, rather than whatever `T::Struct` answered to in the
299
- generating process: deriving them made generation depend on require order, so
300
- with ActiveSupport loaded first a key named `asJson` was refused and loaded
301
- second it became a prop that shadowed the real `#as_json`. The list is what a
302
- struct answers — `T::Struct` and `Object`'s public instance methods, the
303
- hooks Ruby and Rails call on an object that doesn't define one (`initialize`,
304
- `to_ary`, `to_hash`, `to_json`, `as_json`, `to_param`, `try`, `presence`,
305
- `each`, `deconstruct_keys`), and the methods the gem's own mixins define — so
306
- a few more names move than 0.6.1 touched. Kernel's *private* methods are not
307
- on it: `format`, `select`, `test`, `open`, `load` and `pp` are ordinary
308
- column names, and the gem's mixins qualify their own calls (`Kernel.raise`)
309
- so a prop may take one. A federation `@key` on such a field follows the same
310
- rule instead of being refused: the kwarg takes the underscore
311
- (`Representations.room(class_: …)`) and `"class"` still goes on the wire, so
312
- **regenerate if a `@key` of yours names one**. Generated source marks each
313
- rename on the line above the prop — `# wire: class — reserved as a prop
314
- name` — so **read the regenerate diff** rather than grepping for the names
315
- yourself.
255
+ `class` becomes `class_`, and so on for `hash`, `display`, `to_json`, `each` and
256
+ (on an input) `supplied`. Only the Ruby name moves: the wire keeps the schema's
257
+ spelling in both directions, so a refusal on that field still reports `#path`
258
+ `["class"]`, and an input struct's `#to_h` is still the wire hash. A key you
259
+ aliased in the query to get past the old refusal still generates from that alias
260
+ **drop the alias and regenerate** if you want the field's own name back. The
261
+ names that take an underscore are a list the gem owns (`T::Struct` and `Object`'s
262
+ public instance methods, the hooks Ruby and Rails call on an object, and the
263
+ gem's own mixins) rather than whatever the generating process happened to have
264
+ loaded, so a few more names move than 0.6.1 touched; Kernel's *private* methods
265
+ are not on it, so `format`, `select`, `open` and `load` stay ordinary props. A
266
+ federation `@key` on such a field follows the same rule instead of being refused
267
+ the kwarg takes the underscore and `"class"` still goes on the wire, so
268
+ **regenerate if a `@key` of yours names one**. Generated source marks each rename
269
+ on the line above the prop, so **read the regenerate diff**.
316
270
  - **If you adopt `GraphWeaver.graph`, every queries directory needs a graph.**
317
- Declaring one replaces the implicit graph your top-level settings describe,
318
- so an app that declares a graph beside its existing `app/graphql/queries`
319
- leaves that directory unread `generate` skipping it, `verify` calling the
320
- tree up to date. `generate!`, `verify_generated!` and `check_queries` refuse
321
- instead, naming the stray files. **Name the directory in a graph
322
- (`queries`/`output`), declare a graph for it, or delete it.** An app that
323
- declares no graph is unaffected.
271
+ Declaring one replaces the implicit graph your top-level settings describe, so a
272
+ graph declared beside an existing `app/graphql/queries` used to leave that
273
+ directory unread. `generate!`, `verify_generated!` and `check_queries` refuse
274
+ now, naming the stray files. **Name the directory in a graph, declare a graph for
275
+ it, or delete it.** An app that declares no graph is unaffected.
324
276
  - **A `client` that isn't a constant is refused at generation.** Its value is
325
- spelled into every module the graph generates, so `client` given an endpoint
326
- url emitted a file that doesn't parse, from a run that reported success.
327
- Declare the constant and name it — `CLIENT = GraphWeaver.new(url)`, then
328
- `client "CLIENT"` — which is what the message says.
277
+ spelled into every module the graph generates, so `client` given an endpoint url
278
+ emitted a file that doesn't parse, from a run that reported success. Declare the
279
+ constant and name it — `CLIENT = GraphWeaver.new(url)`, then `client "CLIENT"`.
329
280
  - **A `cast:` or `serialize:` proc that returns a value is refused at
330
281
  registration.** A proc there builds *source* for the generated file, so
331
- `cast: ->(v) { v.to_sym }` interpolated to nothing and every response failed
332
- far from the registration, blaming the codec. It is probed once when
333
- registered now: return the source (`cast: ->(v) { "Money.parse(#{v})" }`) or
334
- name a method instead (`cast: :parse`).
282
+ `cast: ->(v) { v.to_sym }` interpolated to nothing and every response failed far
283
+ from the registration. It is probed once when registered now: return the source
284
+ (`cast: ->(v) { "Money.parse(#{v})" }`) or name a method instead.
335
285
  - **`config.graph_weaver` refuses a key the railtie doesn't read**, at boot. It
336
- takes `watch`; `config.graph_weaver.queries_paths = …` was taken silently and
337
- did nothing, so the refusal replaces a line that wasn't working — in every
338
- spelling of that write, `config.graph_weaver[:queries_paths] = …` included.
339
- **Move it to `GraphWeaver.queries_paths =`**, which is what the message says.
340
- - **A router's `fake:` refuses `seed:`**, as `graphql_fake` already did. A
341
- router is built once for the suite, so a seed inside
342
- `graphql_router(fake: …)` would pin every example to one run `rspec --seed
343
- 1234` reproduces the fabricated data along with the test order, and
344
- `GraphWeaver::Testing.config.seed` is the override for a harness that isn't
345
- rspec.
346
-
347
- ## Upgrading from 0.5.1
348
-
349
- Much smaller than 0.5.0, and mostly mechanical. Three commands find most of it:
350
-
351
- ```sh
352
- rake graph_weaver:generate # 1. what codegen emits moved in several places
353
- srb tc # 2. kwargs that got narrower are call-site errors
354
- bundle exec rspec # 3. every deleted knob raises where it's still set
355
- ```
356
-
357
- The rest of this section is what those three don't catch.
358
-
359
- ### Loose input coerces, so `coerce:` and `auto_coerce` are gone
360
-
361
- `execute(first: params[:first])` converts the String to an `Integer` — for every
362
- variable and every input-object field, with nothing to switch on. The old way of
363
- buying that was `GraphWeaver.auto_coerce` or `register_scalar(…, coerce: true)`,
364
- and both paid for it by **widening the emitted kwarg**, which switched off the
365
- static check at every call site. Delete them:
366
-
367
- ```ruby
368
- GraphWeaver.auto_coerce = true # gone
369
- GraphWeaver.register_scalar("Money", Money, coerce: true) # drop the coerce:
370
- ```
371
-
372
- Behavior is unchanged; the kwarg is not. It is now typed exactly as the schema
373
- types it, so a call site passing a **literal** of the wrong type is a new
374
- `srb tc` error — which is the point, since a literal is one you can just spell
375
- right:
376
-
377
- ```ruby
378
- StargazersQuery.execute(first: "10") # srb tc error now
379
- StargazersQuery.execute(first: params[:first]) # fine, and "10" becomes 10
380
- ```
381
-
382
- `cast:` is what a loose value converts through, so a custom scalar needs nothing
383
- beyond the registration it already has. Bad input raises
384
- `GraphWeaver::InputError` naming the variable, the operation and the value.
385
-
386
- Two conversions got **stricter** at the same time, and either can bite an app
387
- that was passing. A numeric string is now read as a wire format rather than as
388
- Ruby source, so `"010"` is ten rather than eight and `"0x1f"` and `"1_0"` are
389
- refused. And a `Boolean` refuses a String outright — every rule for `"0"` and
390
- `"off"` is somebody's convention, so convert at the call site.
391
-
392
- ### `nil` sends `null`
393
-
394
- A variable passed `nil` now sends an explicit `null`; one left out is still left
395
- out. That's what lets a mutation clear a field — and it changes what a kwarg fed
396
- a possibly-missing value means:
397
-
398
- ```ruby
399
- UpdateProfile.execute!(bio: params[:bio]) # a missing param used to omit; now it clears the bio
400
- ```
401
-
402
- **Grep for kwargs fed straight from `params` or an optional attribute**, and
403
- pass the keyword only when you mean it:
404
-
405
- ```ruby
406
- UpdateProfile.execute!(**(params[:bio] ? { bio: params[:bio] } : {}))
407
- ```
408
-
409
- Non-null variables are unaffected: they can't carry `null`, so `nil` there still
410
- omits and the schema default applies. Input objects get the distinction only
411
- where a Hash can express it — `coerce({nickname: nil})` sends null, `coerce({})`
412
- omits, and a struct built with `.new` can't tell the two apart, so `nil` there
413
- still means omit.
414
-
415
- ### Renames
416
-
417
- | before | after |
418
- |---|---|
419
- | `Retry.new(tries: n)`, `retries: { tries: n }` | `retries: n - 1` — one word everywhere, counting the attempts *after* the first, so `retries: 0` is one attempt and `GraphWeaver.new(url, retries: 3)` is four |
420
- | `GraphWeaver.new(url, retries: { retries: 5, retry_codes: […] })` | `GraphWeaver.new(url, retries: 5, retry_codes: […])` — the other retry options sit beside the count; the Hash form read as a key nested in itself |
421
- | `Retry.new(t, on: […])` | `Retry.new(t, retry_on: […])` |
422
- | `Retry.new(t, base: 0.5, max: 30)` | `Retry.new(t, base_delay: 0.5, max_delay: 30)` — beside a count, `max: 30` read as a second, larger attempt count |
423
- | `Codegen.generate(module_name:)` | `name:` — the spelling `GraphWeaver.parse` already used; `module_name:` now raises, naming its replacement |
424
- | `Testing.config.null_chance = 0.3` | `graphql_fake(null_chance: 0.3)`, on the example that wants it |
425
- | `Testing.config.mode = :literal` | `graphql_fake(values: :literal)`, likewise |
426
- | `Testing::MODES` | `Testing::VALUE_STYLES` |
427
- | `SchemaLoader.stale?(path)` | `SchemaLoader.diff(path).empty?` — and `diff` also names what moved |
428
-
429
- The two `Testing.config` deletions are the ones worth a sentence. A suite-wide
430
- `null_chance` answers a per-example question, so it nils an unrelated field one
431
- run in ten, on a seed the failure doesn't name; move it onto the examples that
432
- are *about* an empty state. (`config.default_mode` and the `graphql: :fake` tag
433
- are untouched — the per-fake `mode:` became `values:` so the two can't be
434
- confused for each other.) Every retry misspelling raises rather than being
435
- ignored: the Hash form names its flat replacement, and a retry option passed
436
- without `retries:` says so.
437
-
438
- ### The internals moved behind `Internal`
439
-
440
- The public surface is now what the docs name, what generated code calls, and the
441
- `execute` slot; everything else sits under `GraphWeaver::Internal` or went
442
- `private`, and a spec diffs the two so the next accidental promotion fails CI.
443
- Nothing documented moved — skip this section unless `srb tc` or a
444
- `NoMethodError` says otherwise.
445
-
446
- What a suite might plausibly have reached for: the federation query planner and
447
- its IR (`Internal::Planner`), the fake-value engine (`Internal::Values`), the
448
- selection walk (`Internal::Selection` — so `FakeClient` no longer answers
449
- `each_field` or `gather`), the cassette matching rules (`Internal::RequestKey`),
450
- subgraph detection (was `Testing::Subgraphs`), `GraphWeaver.log` /
451
- `.instrument` / `.filter_variables` (`Internal::Log` — `logger=`,
452
- `instrumenter=` and `filter_parameters=` are unchanged), and
453
- `Transport.operation_name` / `.mutation?` / `.log_tag`, which left the class you
454
- subclass for `Internal::Wire`.
455
-
456
- Two smaller edges. `SchemaDiff::Change`, `Cassette::Check`, `Coverage::Result`
457
- and `InputStruct::Field` are `Data` now rather than `Struct`, so they hand out
458
- no writers — read one, build a new one to change a field. And generated modules
459
- keep their own plumbing to themselves: `DEFAULT_CLIENT`, `FIELDS` and `ONE_OF`
460
- are emitted `private_constant`, so **regenerate**.
461
-
462
- ### Behavior that changed under you
463
-
464
- - **A mutation is no longer retried.** A timeout doesn't say whether the server
465
- applied it, and a second `charge` is worse than a failed one. Pass
466
- `retry_mutations: true` for an API whose mutations are idempotent.
467
- - **A registration this schema can't match warns instead of failing
468
- generation.** One registry serves a whole federated graph, so a name the
469
- schema in hand doesn't declare may belong to the subgraph next door — see
470
- [federation](federation.md#generating-for-a-federated-graph). Your typo is now
471
- in the list `rake graph_weaver:generate` prints after the files, so read it.
472
- - **`verify_generated!` fails when it finds no query documents.** A mistyped
473
- `queries_paths` used to leave a CI gate green forever.
474
- - **The local router refuses a `@fromContext` argument** rather than fetching
475
- the field with it unset. Federation 2.8's `@context` machinery was on the
476
- routing table's known list, so the argument was read and dropped. Per query,
477
- like `@interfaceObject`: a subtree one subgraph answers whole still runs.
478
- - **A `#trace` assertion may see one entry fewer.** Two `@requires` field sets
479
- crossing into the same subgraph on the same `@key` now ride one entity fetch,
480
- the way Apollo's do.
481
- - **Fabricating a custom scalar registered as a class of your own needs a pin
482
- for the type** — `Testing.config.overrides = { "Money" => "12.00" }`, or the
483
- same key on one example's `graphql_fake`. Without one, `FakeClient` and
484
- cassette anonymization refuse rather than feeding your cast a `"Money-1"`
485
- placeholder. Scalars registered as `BigDecimal`, `Time`, `Date`, `Integer`,
486
- `Float`, `String` or `T::Boolean` need nothing.
487
- - **Re-run `rake graph_weaver:cassettes:anonymize`** on any committed cassette
488
- holding a registered custom scalar: the anonymizer used to write a value the
489
- generated codec couldn't read back.
490
- - **Generation refuses four more things**, each naming its fix — a
491
- `register_scalar` whose Ruby type nothing can build out of JSON (a value
492
- object of your own: give it a `cast:`), a result key that would shadow a constant the
493
- file uses, an enum value that camelizes to nothing, and a narrowed fragment
494
- whose `__typename` sits behind `@skip`/`@include`.
495
-
496
- ## Upgrading to 0.5.0
497
-
498
- 0.5.0 is one large breaking release. Almost all of it is caught mechanically,
499
- in this order:
500
-
501
- ```sh
502
- # 1. rename the path settings first — generate won't load without them
503
- # (queries_path -> queries_paths, generated_path -> generated_paths,
504
- # fragments_path -> fragments_paths; see "Path settings are lists" below)
505
-
506
- bundle exec tapioca gem graph_weaver # 2. regenerate the RBI
507
- rake graph_weaver:generate # 3. the emitted call shape changed
508
- srb tc # 4. every call site that moved is an error
509
- rake graph_weaver:verify # 5. fails until the tree is regenerated
510
- ```
511
-
512
- **Step 2 is not optional.** Against the 0.4.6 RBI, `srb tc` reports errors
513
- pointing into your `generated/` directory — `QueryModule`, `client_for`,
514
- `check_envelope!` — which read as though codegen emitted broken Ruby. It
515
- didn't; sorbet is checking new generated code against the old gem's types.
516
- Regenerate the RBI and what remains is only your own call sites.
517
-
518
- Generated code is `# typed: strict`, so step 4 finds those for you. The rest of
519
- this page is what a typechecker can't see.
520
-
521
- ### `execute` means one thing now
522
-
523
- Every client answers the same call — `execute(query, variables:, operation_name:)`,
524
- returning the raw response hash. `Client` used to spell something else under
525
- that name, which is why `Retry.new(client)` and `Sequence.new(client, fake)`
526
- raised `ArgumentError`. They work now.
527
-
528
- The one-shot sugar moved to `run`:
529
-
530
- ```ruby
531
- client.execute!("query { … }", id: "1") # before
532
- client.run!("query { … }", id: "1") # after (and #run for the envelope)
533
-
534
- GraphWeaver.execute(source, query, **vars) # before
535
- GraphWeaver.run(source, query, **vars) # after
536
- ```
537
-
538
- **This one is worth grepping for.** `Client#execute` still exists, so a stale
539
- call fails at runtime rather than at typecheck — as do `GraphWeaver.execute`
540
- and `GraphWeaver.reset_scalars!`, which are simply gone and will not be flagged
541
- until the RBI is regenerated (step 2): `rg '\.execute!?\(' --type ruby`
542
- and check each hit is passing `variables:` rather than loose kwargs.
543
-
544
- A generated module takes its per-call client as a **keyword**:
545
-
546
- ```ruby
547
- PersonQuery.execute(some_client, id: "1") # before
548
- PersonQuery.execute(client: some_client, id: "1") # after
549
- ```
550
-
551
- `GraphWeaver.resolve_transport` is gone; nothing needs unwrapping any more.
552
-
553
- ### Path settings are lists
554
-
555
- `queries_paths`, `generated_paths`, `fragments_paths` — every entry is read.
556
- Assigning a String still works, so the change is the name:
557
-
558
- ```ruby
559
- GraphWeaver.queries_path = "app/graphql/queries" # before
560
- GraphWeaver.queries_paths = "app/graphql/queries" # after
561
- ```
562
-
563
- `schema_path` stays singular: one run reads one schema.
564
-
565
- ### One reset
566
-
567
- `GraphWeaver.reset_registrations!` is the clean slate between tests, or between
568
- generations for different schemas. The four
569
- narrow ones moved to where they live:
570
-
571
- ```ruby
572
- GraphWeaver.reset_scalars! # before
573
- GraphWeaver::Codegen.reset_scalars! # after (also reset_enums!, clear_scalars!,
574
- # reset_type_helpers!)
575
- ```
576
-
577
- ### Generated names come from the response key, not the type
578
-
579
- Nested structs used to be named for the GraphQL *type* they were cast from;
580
- they are now named for the **response key that selects them**, camelized, and
581
- the constant path reads like the query. The typechecker finds the call sites in
582
- a `# typed: true` file (an unresolved constant is an `srb tc` error); in a
583
- `# typed: false` file it is `uninitialized constant` at runtime, so grep for
584
- `::Result::` there.
585
-
586
- | selection | before (type) | after (key) |
587
- |---|---|---|
588
- | `person { pets { name } }` | `PersonQuery::Result::Person::Pet` | `PersonQuery::Result::Person::Pets` |
589
- | `payrollRisk { score }` | `…::Result::RiskAssessment` | `…::Result::PayrollRisk` |
590
- | `_entities(…) { ... on Product { … } }` | `…::Result::Product` | `…::Result::Entities::Product` |
591
-
592
- The key is used verbatim — no pluralization, so a list field `pets` is `Pets`.
593
- To pick the name yourself, alias the field: `pet: pets { name }` generates
594
- `Pet`. Union and interface members keep their type-condition names, nested in
595
- the container the field names. The payoff is that adding, removing or
596
- reordering an unrelated selection can never rename a struct you reference.
597
-
598
- **Enums moved out of the result tree.** Every schema enum a query touches is one
599
- Ruby type in the shared module, `GraphQLTypes::Species`, so a value read from
600
- one query hands straight into another's variable. A query module aliases the
601
- enums its *variables* use (`AddPetMutation::Species` still works); an enum
602
- reached only through a result is no longer nested under the struct that
603
- carries it — `SearchQuery::Result::Search::Species` is `GraphQLTypes::Species`.
604
-
605
- ### Smaller renames
606
-
607
- | before | after |
608
- |---|---|
609
- | `Testing.config.auto_fake = true` | `Testing.config.default_mode = :fake` |
610
- | a mutation's `…Query` module | `…Mutation` |
611
- | `graphql: :none` (rspec tag) | `graphql: :live` |
612
-
613
- **The shared types module was three, and is now one.** `GraphQLInputs`,
614
- `GraphQLEnums` and `GraphQLUnions` are all `GraphQLTypes`, and the files move
615
- with them — `generated/inputs/` becomes `generated/types/`. The three settings
616
- that named them (`inputs_module=`, `enums_module=`, `unions_module=`) are one
617
- `types_module=`. Regenerating writes the new tree; delete the old directory,
618
- which pruning leaves behind empty.
619
-
620
- If your specs run one schema class in-process while your client points at a
621
- different API, name it — per example, since a federated suite runs more than
622
- one:
623
-
624
- ```ruby
625
- graphql_in_process(MySchema) # in the example
626
- GraphWeaver::Testing.config.schema = MySchema # or once, for the whole suite
627
- ```
628
-
629
- ### Registering from Rails
630
-
631
- A registration naming one of your own constants belongs in a `to_prepare` block
632
- — the same place the in-process client goes, and for the same reason:
633
- autoloading is set up after `config/initializers` run.
634
-
635
- ```ruby
636
- Rails.application.config.to_prepare do
637
- GraphWeaver.register_enum("Species", PetKind, fallback: PetKind::Unknown)
638
- GraphWeaver.extend_type("Pet", PetHelpers)
639
- end
640
- ```
641
-
642
- Generation depends on `:environment`, which runs `to_prepare` too, so the
643
- registration is in place before it emits — and at boot the generated files
644
- load from a `to_prepare` block of their own, after yours.
645
-
646
- ### If you use the federation router
647
-
648
- Detection only sees *loaded* schema classes, and Rails does not eager load for
649
- rake or in the default test environment. Both are one line:
650
-
651
- ```ruby
652
- config.eager_load = true # config/environments/test.rb
653
- config.rake_eager_load = true # config/application.rb
654
- ```
655
-
656
- Without them the `federation:*` tasks silently see nothing — and
657
- `federation:diff` now **fails** rather than reporting a green "matches" over
658
- zero subgraphs.
286
+ takes `watch`; `config.graph_weaver.queries_paths = …` was taken silently and did
287
+ nothing. **Move it to `GraphWeaver.queries_paths =`.**
288
+ - **A router's `fake:` refuses `seed:`**, as `graphql_fake` already did. A router is
289
+ built once for the suite, so a seed there would pin every example to one run —
290
+ `rspec --seed 1234` reproduces the fabricated data along with the test order, and
291
+ `GraphWeaver::Testing.config.seed` is the override for a harness that isn't rspec.
292
+ - **Check that your `require "graph_weaver/rspec"` actually runs.** The old setup
293
+ put it in `spec/support/graph_weaver.rb`, and rspec-rails ships the `spec/support`
294
+ glob **commented out** so if you never uncommented it, the tag did nothing and
295
+ every `graphql: :fake` example has been hitting the real client.
296
+ `rails g graph_weaver:install` writes the line into `spec/rails_helper.rb`
297
+ instead; **move yours there** if the glob isn't live.
298
+ - **`graphql: :wire`, if you adopt it, needs webmock *enabled*** — `require
299
+ "webmock/rspec"` in the spec helper. Having it in the Gemfile is not enough:
300
+ `Bundler.require` loads webmock without installing its adapters, and the tag
301
+ refuses before the first request.
302
+ - **Regenerate**, as ever. Generated modules carry a private `GRAPH` naming the
303
+ graph they were generated from, and a
304
+ [multi-schema](getting_started.md#more-than-one-schema) app whose modules predate
305
+ it refuses rather than guessing which schema a module belongs to. A generated
306
+ `execute` also makes its request through the gem now, which is what lets an event
307
+ name the graph; 0.6.1's modules keep working, but `verify` reports the tree out of
308
+ date until you regenerate. Result structs also gained `==`/`eql?`/`hash`,
309
+ `deconstruct_keys`, `#to_h` and `#as_json`.