graph_weaver 0.2.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -0
- data/Gemfile.lock +21 -30
- data/PLAN.md +7 -0
- data/docs/generated_modules.md +78 -4
- data/docs/getting_started.md +8 -6
- data/docs/testing.md +21 -14
- data/lib/graph_weaver/client.rb +3 -2
- data/lib/graph_weaver/codegen/emit.rb +274 -60
- data/lib/graph_weaver/codegen/nodes.rb +35 -80
- data/lib/graph_weaver/codegen.rb +41 -104
- data/lib/graph_weaver/input_struct.rb +59 -0
- data/lib/graph_weaver/railtie.rb +2 -1
- data/lib/graph_weaver/rspec.rb +4 -6
- data/lib/graph_weaver/testing.rb +4 -4
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +103 -22
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7673fd6efc0459bdb3097e73e1c3f8fe265910bfdaf349bf7a358adc1ece1830
|
|
4
|
+
data.tar.gz: f5ce9f4c484e1fb8f5eb074eaaac502ca62fb3ecf6ab90d9157a2a055e88c3b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a75dd5a723dd52108e2f449bd1c5f0e1dc9a71065106614c57854f318024f700e16596cd4764cf9768f02d536cc040bd0554bdd75762eeba23c09b71a7051cf5
|
|
7
|
+
data.tar.gz: 377c07a20d2cf47873425c72ad84709c36555396fea1a2102c7bdc3f79e07e8e823ed9639238aecac8066f352e65599a10ae15a71013a7de3a0a4d3675bf169c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
### v0.2.2 (2026-07-22)
|
|
2
|
+
- Generated modules expose from_response / from_response! alongside
|
|
3
|
+
execute / execute!: deserialize a raw GraphQL response (fetched by any
|
|
4
|
+
client) into the typed envelope without going through the transport.
|
|
5
|
+
execute now delegates to from_response
|
|
6
|
+
|
|
7
|
+
### v0.2.1 (2026-07-13)
|
|
8
|
+
- Conventional paths are appendable lists: queries_paths /
|
|
9
|
+
generated_paths (singular accessors read the first entry, so existing
|
|
10
|
+
config keeps working); load_generated!, Client#load_queries!, and the
|
|
11
|
+
Railtie walk every entry — append spec/support/graphql/* from a spec
|
|
12
|
+
helper to load test-only queries. Entries may be globs, and the
|
|
13
|
+
generated default includes app/graphql/*/generated so per-schema
|
|
14
|
+
layouts auto-load
|
|
15
|
+
- inputs_module derives from the output path: multi-schema layouts name
|
|
16
|
+
each schema's module after its directory
|
|
17
|
+
(app/graphql/github/generated -> GithubInputs), the conventional
|
|
18
|
+
layout keeps GraphQLInputs; GraphWeaver.inputs_module= and
|
|
19
|
+
generate!(inputs_module:) still override
|
|
20
|
+
- Shared types split one-file-per-type: generated/inputs/ holds each
|
|
21
|
+
input struct/enum in its own small file (PokeAPI: 573 files, median
|
|
22
|
+
24 lines vs one 11.5k-line blob) with inputs.rb as the manifest
|
|
23
|
+
(forward declarations make load order irrelevant); regeneration
|
|
24
|
+
prunes files for types the schema dropped, verify flags strays;
|
|
25
|
+
generate!/verify take inputs_module: per invocation (multi-schema
|
|
26
|
+
apps generate into different modules)
|
|
27
|
+
- Shared input types: generate! emits every variable type (input
|
|
28
|
+
structs + their enums + mapped-enum tables) ONCE per schema into
|
|
29
|
+
generated/inputs.rb (module GraphQLInputs; GraphWeaver.inputs_module=
|
|
30
|
+
renames, shared_inputs: false opts out), with query modules aliasing
|
|
31
|
+
only what their own surface references — AdoptQuery::AdoptionInput
|
|
32
|
+
keeps working and shared types gain one identity across modules.
|
|
33
|
+
Three filtered Hasura queries: 34,684 lines inline -> 11,754 shared
|
|
34
|
+
(~90 lines per query module)
|
|
35
|
+
- BREAKING (vs 0.2.0): auto_fake is opt-in again — require
|
|
36
|
+
"graph_weaver/rspec" no longer swaps every example onto a fake;
|
|
37
|
+
set config.auto_fake = true explicitly (the schema still auto-locates
|
|
38
|
+
once you do). Less magic, no unexpected behavior
|
|
39
|
+
- Generated input structs are table-driven: typed consts + a per-field
|
|
40
|
+
FIELDS table (conversions as lambdas) interpreted by the
|
|
41
|
+
GraphWeaver::InputStruct runtime, replacing unrolled
|
|
42
|
+
serialize/coerce/value_at per struct — a bool_exp-heavy PokeAPI module
|
|
43
|
+
shrinks 29k -> 11.5k lines (-60%) with identical behavior (nil
|
|
44
|
+
omission, wire-value enums, nested/recursive coercion, spellchecked
|
|
45
|
+
unknown keys all covered by the existing suite)
|
|
46
|
+
- Internal: Node base class for the codegen IR protocol; module
|
|
47
|
+
assembly moved from Codegen#generate into Emit#emit_module
|
|
48
|
+
(byte-identical output)
|
|
49
|
+
|
|
1
50
|
### v0.2.0 (2026-07-12)
|
|
2
51
|
- Cleanup pass (staff-engineer review): scalar registrations get the
|
|
3
52
|
same typo validation as enums/types; cassette replay stops recomputing
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
graph_weaver (0.2.
|
|
4
|
+
graph_weaver (0.2.2)
|
|
5
5
|
graphql (>= 2)
|
|
6
6
|
sorbet-runtime
|
|
7
7
|
|
|
@@ -19,7 +19,6 @@ GEM
|
|
|
19
19
|
irb (~> 1.10)
|
|
20
20
|
reline (>= 0.3.8)
|
|
21
21
|
diff-lcs (1.6.2)
|
|
22
|
-
docile (1.4.1)
|
|
23
22
|
erb (6.0.4)
|
|
24
23
|
erubi (1.13.1)
|
|
25
24
|
faker (3.8.0)
|
|
@@ -43,7 +42,7 @@ GEM
|
|
|
43
42
|
google-protobuf (4.35.1-x86_64-linux-gnu)
|
|
44
43
|
bigdecimal
|
|
45
44
|
rake (~> 13.3)
|
|
46
|
-
graphql (2.6.
|
|
45
|
+
graphql (2.6.6)
|
|
47
46
|
base64
|
|
48
47
|
fiber-storage
|
|
49
48
|
logger
|
|
@@ -100,21 +99,16 @@ GEM
|
|
|
100
99
|
rubydex (0.2.7-arm64-darwin)
|
|
101
100
|
rubydex (0.2.7-x86_64-darwin)
|
|
102
101
|
rubydex (0.2.7-x86_64-linux)
|
|
103
|
-
simplecov (0.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
sorbet (0.6.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
sorbet-static (0.6.13327-universal-darwin)
|
|
114
|
-
sorbet-static (0.6.13327-x86_64-linux)
|
|
115
|
-
sorbet-static-and-runtime (0.6.13327)
|
|
116
|
-
sorbet (= 0.6.13327)
|
|
117
|
-
sorbet-runtime (= 0.6.13327)
|
|
102
|
+
simplecov (1.0.2)
|
|
103
|
+
sorbet (0.6.13347)
|
|
104
|
+
sorbet-static (= 0.6.13347)
|
|
105
|
+
sorbet-runtime (0.6.13347)
|
|
106
|
+
sorbet-static (0.6.13347-aarch64-linux)
|
|
107
|
+
sorbet-static (0.6.13347-universal-darwin)
|
|
108
|
+
sorbet-static (0.6.13347-x86_64-linux)
|
|
109
|
+
sorbet-static-and-runtime (0.6.13347)
|
|
110
|
+
sorbet (= 0.6.13347)
|
|
111
|
+
sorbet-runtime (= 0.6.13347)
|
|
118
112
|
spoom (1.8.3)
|
|
119
113
|
erubi (>= 1.10.0)
|
|
120
114
|
prism (>= 0.28.0)
|
|
@@ -172,7 +166,6 @@ CHECKSUMS
|
|
|
172
166
|
concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
|
|
173
167
|
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
|
|
174
168
|
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
175
|
-
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
|
|
176
169
|
erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
|
|
177
170
|
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
|
178
171
|
faker (3.8.0) sha256=c147b308df73a90f27a4fc84f18d4c22ef0ad9c2a64b2b61c86fd0ca71753efc
|
|
@@ -183,8 +176,8 @@ CHECKSUMS
|
|
|
183
176
|
google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
|
|
184
177
|
google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
|
|
185
178
|
google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
|
|
186
|
-
graph_weaver (0.2.
|
|
187
|
-
graphql (2.6.
|
|
179
|
+
graph_weaver (0.2.2)
|
|
180
|
+
graphql (2.6.6) sha256=7438e2036b571c884377eb1529225470ba959365f26dc47152953c213bc38c31
|
|
188
181
|
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
|
|
189
182
|
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
190
183
|
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
|
|
@@ -213,15 +206,13 @@ CHECKSUMS
|
|
|
213
206
|
rubydex (0.2.7-arm64-darwin) sha256=f0d28bbf4153568be79b671642424750053e0bea971b60ddf5cec19bf4563990
|
|
214
207
|
rubydex (0.2.7-x86_64-darwin) sha256=b002b259d118ac69de44470eff1597143318402c45630c47371f9542631447dc
|
|
215
208
|
rubydex (0.2.7-x86_64-linux) sha256=dacfade9fa42ce4469618da6dac07e69d5f3ac6a313b4caced5234c8f052419a
|
|
216
|
-
simplecov (0.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
sorbet (0.6.
|
|
220
|
-
sorbet-
|
|
221
|
-
sorbet-static (0.6.
|
|
222
|
-
sorbet-static (0.6.
|
|
223
|
-
sorbet-static (0.6.13327-x86_64-linux) sha256=f3a915bab4af1979145c793a08d3b1db207526d5e2f332513da10a95e70f9028
|
|
224
|
-
sorbet-static-and-runtime (0.6.13327) sha256=7b039be023b6a7d2d17b1f751191f32307d9ace4170b6a192bb0e005d222e00e
|
|
209
|
+
simplecov (1.0.2) sha256=c6459434efe4b948b46477cc2df2faa73ab365f83a33c7c17f81262f4f7f1244
|
|
210
|
+
sorbet (0.6.13347) sha256=2830946e6efda8dc732c5d703350bed17f437fbea1ccfbef543a65e33ae445c1
|
|
211
|
+
sorbet-runtime (0.6.13347) sha256=54e7221d9f4b63f58aec79f3043752a9ba7810347c6ae0d903531591cc2d5f01
|
|
212
|
+
sorbet-static (0.6.13347-aarch64-linux) sha256=9bb60509bf9a8715d93e77cd55f96659575d81a5f47b52bd4f66b3dfe6f2182e
|
|
213
|
+
sorbet-static (0.6.13347-universal-darwin) sha256=d12678a45369dba9db724103d8badda995e66a83ae35798f68d804c46a6d3e45
|
|
214
|
+
sorbet-static (0.6.13347-x86_64-linux) sha256=5a4803d2ba4fc1964e23f3aa4073b224c345c2a72a5f5317623a4bae7ce2c8da
|
|
215
|
+
sorbet-static-and-runtime (0.6.13347) sha256=2130e3c20326948a3edd20385b444345cb7327eea9b141f373486e7c087a498d
|
|
225
216
|
spoom (1.8.3) sha256=32871fa189bbfa49cf557a50f819f23cc9a6ceefd0346caa7a6adc193becd5dd
|
|
226
217
|
tapioca (0.19.2) sha256=938731b07811aee8d23871b1aee8861d464fbaf2cfffbf79a62b0c869a5120ec
|
|
227
218
|
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|
data/PLAN.md
CHANGED
|
@@ -105,6 +105,13 @@ Response envelope; execute! for raise-or-result).
|
|
|
105
105
|
|
|
106
106
|
## From the field-test experiments (2026-07-12)
|
|
107
107
|
|
|
108
|
+
Generated-module size (2026-07-12, post-0.2.0): input structs are now
|
|
109
|
+
table-driven (InputStruct runtime + FIELDS) — the PokeAPI filtered-query
|
|
110
|
+
module dropped 29,233 -> 11,562 lines. The floor is ~2 lines/field
|
|
111
|
+
(typed const + FIELDS entry). Remaining lever if it matters again:
|
|
112
|
+
shared input structs emitted once per schema instead of per module.
|
|
113
|
+
|
|
114
|
+
|
|
108
115
|
A junior + senior agent pair exercised the repo cold (clone, examples,
|
|
109
116
|
extensions, a Pokedex app against Hasura's 4,441-type PokeAPI schema).
|
|
110
117
|
Fixed same-day: snake_case type names generated invalid constants
|
data/docs/generated_modules.md
CHANGED
|
@@ -21,7 +21,10 @@ fails when the two drift. The conventional layout (configurable via
|
|
|
21
21
|
app/graphql/
|
|
22
22
|
schema.json # introspection dump (or schema.graphql SDL)
|
|
23
23
|
queries/ # *.graphql — hand-written, reviewed
|
|
24
|
-
generated/
|
|
24
|
+
generated/
|
|
25
|
+
inputs.rb # manifest: requires + forward declarations
|
|
26
|
+
inputs/ # one file per shared type (input structs, enums)
|
|
27
|
+
*_query.rb # one module per query — generated, checked in, never edited
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
The schema dump is step 0 — codegen reads it, never a live endpoint.
|
|
@@ -58,6 +61,20 @@ first). Elsewhere it's explicit, factory_bot-style:
|
|
|
58
61
|
GraphWeaver.load_generated! # require every file under generated_path
|
|
59
62
|
```
|
|
60
63
|
|
|
64
|
+
The conventional paths are lists (entries may be globs — the default
|
|
65
|
+
includes `app/graphql/*/generated`, so per-schema layouts load too).
|
|
66
|
+
Append extra locations (a test-only schema, an engine's queries) and
|
|
67
|
+
every loader walks them all:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# e.g. in spec/support/graph_weaver.rb
|
|
71
|
+
GraphWeaver.generated_paths << "spec/support/graphql/generated"
|
|
72
|
+
GraphWeaver.queries_paths << "spec/support/graphql/queries"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The singular accessors (`generated_path` etc.) read and replace the
|
|
76
|
+
first entry — the default target for `generate!` and the rake tasks.
|
|
77
|
+
|
|
61
78
|
(Plain requires, not Zeitwerk: Zeitwerk would expect
|
|
62
79
|
`Generated::PersonQuery` from `generated/person_query.rb`, and generated
|
|
63
80
|
code only changes on regeneration — restart, like a schema migration.)
|
|
@@ -102,6 +119,9 @@ module PersonQuery
|
|
|
102
119
|
def self.client ... # default client (see below)
|
|
103
120
|
def self.execute(client = nil, id:) # -> GraphWeaver::Response[Result]
|
|
104
121
|
def self.execute!(client = nil, id:) # -> Result, or raises QueryError
|
|
122
|
+
|
|
123
|
+
def self.from_response(response) # deserialize a raw hash -> Response[Result]
|
|
124
|
+
def self.from_response!(response) # -> Result, or raises QueryError
|
|
105
125
|
end
|
|
106
126
|
```
|
|
107
127
|
|
|
@@ -110,6 +130,38 @@ end
|
|
|
110
130
|
cost/throttle metadata survive.
|
|
111
131
|
- `execute!` is the shortcut: the typed result or a raised
|
|
112
132
|
`GraphWeaver::QueryError`.
|
|
133
|
+
- `from_response` / `from_response!` are the **network-free half** of the
|
|
134
|
+
pair — same envelope, but from a response hash you already have (see below).
|
|
135
|
+
|
|
136
|
+
## Deserializing a response from another client
|
|
137
|
+
|
|
138
|
+
`execute` is two steps: make the request, then cast the JSON into the typed
|
|
139
|
+
structs. Only the second step is GraphWeaver-specific, and it's exposed on its
|
|
140
|
+
own — so you can fetch with any GraphQL client (Apollo, a raw `Net::HTTP` post,
|
|
141
|
+
a batching layer, a recorded fixture) and hand the result to GraphWeaver:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
raw = my_graphql_client.post(PersonQuery::QUERY, id: "1")
|
|
145
|
+
# => {"data" => {"person" => {...}}, "errors" => [...], "extensions" => {...}}
|
|
146
|
+
|
|
147
|
+
response = PersonQuery.from_response(raw) # GraphWeaver::Response[Result]
|
|
148
|
+
person = response.data!.person # typed, no network
|
|
149
|
+
|
|
150
|
+
# or skip the envelope:
|
|
151
|
+
person = PersonQuery.from_response!(raw).person
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`from_response` builds the exact same `GraphWeaver::Response[Result]` envelope
|
|
155
|
+
`execute` does — in fact `execute` *is* `from_response(transport.execute(...))`.
|
|
156
|
+
Errors and extensions are preserved; `#data!` / `from_response!` raise
|
|
157
|
+
`QueryError` on top-level errors.
|
|
158
|
+
|
|
159
|
+
The one requirement: pass the response **verbatim** — a hash (or anything with
|
|
160
|
+
`#to_h`) with the standard GraphQL shape and **wire-cased string keys**
|
|
161
|
+
(`"person"`, `"nameWithOwner"`), the top-level `"data"` / `"errors"` /
|
|
162
|
+
`"extensions"` keys included. Don't symbolize or snake_case it first;
|
|
163
|
+
`from_response` reads `raw["data"]` and the casting reads camelCase field keys.
|
|
164
|
+
The module must, of course, be generated for the query you ran.
|
|
113
165
|
|
|
114
166
|
## Variables become typed kwargs
|
|
115
167
|
|
|
@@ -152,9 +204,31 @@ hint rather than silently dropping):
|
|
|
152
204
|
AdoptQuery.execute!(input: { name: "Rex", species: "DOG" }, detail: true)
|
|
153
205
|
```
|
|
154
206
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
207
|
+
In the generate! workflow, input types (and the enums they use) are
|
|
208
|
+
emitted **once per schema** — one file per type under
|
|
209
|
+
`generated/inputs/`, with `inputs.rb` as the manifest. The module is
|
|
210
|
+
named from the output path: the conventional layout gets
|
|
211
|
+
`GraphQLInputs`, while a multi-schema layout names each schema's module
|
|
212
|
+
after its directory (`app/graphql/github/generated` → `GithubInputs`).
|
|
213
|
+
Override globally with `GraphWeaver.inputs_module=` or per run with
|
|
214
|
+
`generate!(inputs_module:)`; opt out with
|
|
215
|
+
`generate!(shared_inputs: false)`. Per-type files keep schema drift
|
|
216
|
+
reviewable: a migration diffs exactly the types it touched, and types
|
|
217
|
+
the schema drops are pruned on regeneration (`verify` flags strays).
|
|
218
|
+
Query modules alias what they touch,
|
|
219
|
+
so `AdoptQuery::AdoptionInput` still works and shared types keep one
|
|
220
|
+
identity across modules — three filtered Hasura queries cost one ~11k-line
|
|
221
|
+
inputs file plus ~90 lines each, instead of ~35k lines of duplicates.
|
|
222
|
+
Deeply nested types live unaliased in the shared module
|
|
223
|
+
(`GraphQLInputs::PetFilter`). Dynamic `parse` stays self-contained.
|
|
224
|
+
|
|
225
|
+
The structs themselves are module-level (`AdoptQuery::AdoptionInput`):
|
|
226
|
+
typed consts plus a compact per-field `FIELDS` table that the
|
|
227
|
+
`GraphWeaver::InputStruct` runtime drives — `serialize` (aliased `to_h`)
|
|
228
|
+
produces the wire hash with nil optionals omitted, `coerce` builds from
|
|
229
|
+
plain hashes. The conversions ship in the generated file as data
|
|
230
|
+
(lambdas in the table), so a Hasura `bool_exp` pulling hundreds of input
|
|
231
|
+
types stays ~2 lines per field instead of unrolled methods. Nested inputs work (dependencies emit
|
|
158
232
|
first), including recursive ones — Hasura's self-referential `bool_exp`
|
|
159
233
|
filters generate cleanly (`_and:`/`_not:` fields typed as the struct
|
|
160
234
|
itself), so variable-driven filtering works:
|
data/docs/getting_started.md
CHANGED
|
@@ -91,14 +91,16 @@ PersonQuery.execute!(id: "1").person&.name # typed, via GraphWeaver.client
|
|
|
91
91
|
```ruby
|
|
92
92
|
# spec/support/graph_weaver.rb
|
|
93
93
|
require "graph_weaver/rspec"
|
|
94
|
+
|
|
95
|
+
GraphWeaver::Testing.configure { |config| config.auto_fake = true }
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
against a seeded, schema-correct `FakeClient` — no server, no
|
|
99
|
-
and `rspec --seed 1234` reproduces the fake data along with test
|
|
100
|
-
Pin values with `overrides:`, simulate failures with `Failure
|
|
101
|
-
|
|
98
|
+
The opt-in is deliberate (no surprise fakes); once on, the schema
|
|
99
|
+
auto-locates from the committed dump and every query in every example
|
|
100
|
+
executes against a seeded, schema-correct `FakeClient` — no server, no
|
|
101
|
+
stubs, and `rspec --seed 1234` reproduces the fake data along with test
|
|
102
|
+
order. Pin values with `overrides:`, simulate failures with `Failure.*`
|
|
103
|
+
— see [testing](testing.md).
|
|
102
104
|
|
|
103
105
|
## 7. Verify in CI
|
|
104
106
|
|
data/docs/testing.md
CHANGED
|
@@ -31,24 +31,22 @@ GraphWeaver::Testing::FakeClient.new(schema:, overrides: {
|
|
|
31
31
|
})
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
With rspec,
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
With rspec, the setup is two lines in `spec/support/graph_weaver.rb` —
|
|
35
|
+
the require, plus an explicit opt-in to per-example fakes (deliberately
|
|
36
|
+
not a default: silently swapping every example onto a fake would be
|
|
37
|
+
surprising). The schema auto-locates from the committed dump at
|
|
38
|
+
`GraphWeaver.schema_path`:
|
|
37
39
|
|
|
38
40
|
```ruby
|
|
39
|
-
require "graph_weaver/rspec" # seed follows --seed
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Configure to override any of it:
|
|
41
|
+
require "graph_weaver/rspec" # seed follows --seed
|
|
43
42
|
|
|
44
|
-
```ruby
|
|
45
43
|
GraphWeaver::Testing.configure do |config|
|
|
46
|
-
config.
|
|
47
|
-
config.
|
|
48
|
-
config.mode = :faker
|
|
49
|
-
config.overrides = { "Person.name" => "Daniel" }
|
|
50
|
-
config.list_size = 1..3
|
|
51
|
-
config.null_chance = 0.1
|
|
44
|
+
config.auto_fake = true # every example runs against a fresh fake
|
|
45
|
+
# config.schema = MySchema # optional: an in-process class instead of the dump
|
|
46
|
+
# config.mode = :faker # or :literal (plain typed values); nil = auto
|
|
47
|
+
# config.overrides = { "Person.name" => "Daniel" }
|
|
48
|
+
# config.list_size = 1..3
|
|
49
|
+
# config.null_chance = 0.1 # nullable fields go nil sometimes
|
|
52
50
|
end
|
|
53
51
|
```
|
|
54
52
|
|
|
@@ -59,6 +57,15 @@ they consult `GraphWeaver.client`). `mode:` picks value fabrication: `:faker`
|
|
|
59
57
|
(semantic, field-name matched — raises if the gem is missing),
|
|
60
58
|
`:literal` (plain type-derived), or nil to auto-detect faker.
|
|
61
59
|
|
|
60
|
+
Test-only queries don't have to live in `app/` — the conventional paths
|
|
61
|
+
are appendable lists, so the same support file can register a
|
|
62
|
+
spec-local set that `load_generated!` (and the Railtie) pick up:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
GraphWeaver.generated_paths << "spec/support/graphql/generated"
|
|
66
|
+
GraphWeaver.queries_paths << "spec/support/graphql/queries"
|
|
67
|
+
```
|
|
68
|
+
|
|
62
69
|
**Simulating failures** — every failure mode is just a client, so
|
|
63
70
|
error-handling paths are testable without a server that misbehaves on cue:
|
|
64
71
|
|
data/lib/graph_weaver/client.rb
CHANGED
|
@@ -127,8 +127,9 @@ class GraphWeaver::Client
|
|
|
127
127
|
#
|
|
128
128
|
# Reloadable (constants are replaced), so it suits consoles and dev.
|
|
129
129
|
# Returns the modules.
|
|
130
|
-
def load_queries!(dir =
|
|
131
|
-
|
|
130
|
+
def load_queries!(dir = nil, namespace: Object)
|
|
131
|
+
dirs = dir ? [dir] : GraphWeaver.queries_paths
|
|
132
|
+
dirs.flat_map { |d| Dir[File.join(d, "*.graphql")].sort }.map do |path|
|
|
132
133
|
name = "#{GraphWeaver::Inflect.camelize(File.basename(path, ".graphql"))}Query"
|
|
133
134
|
namespace.send(:remove_const, name) if namespace.const_defined?(name, false)
|
|
134
135
|
GraphWeaver.log(:info) { "loaded #{name} from #{path}" }
|