graphlient 0.7.0 → 0.9.0
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/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- data/.github/workflows/ci.yml +33 -29
- data/.github/workflows/danger-comment.yml +10 -0
- data/.github/workflows/danger.yml +13 -22
- data/.github/workflows/rubocop.yml +19 -19
- data/.gitignore +59 -52
- data/.rspec +1 -1
- data/.rubocop.yml +26 -28
- data/.rubocop_todo.yml +72 -48
- data/CHANGELOG.md +128 -107
- data/CONTRIBUTING.md +125 -125
- data/Dangerfile +24 -24
- data/Gemfile +35 -27
- data/LICENSE +21 -21
- data/README.md +800 -508
- data/RELEASING.md +63 -63
- data/Rakefile +15 -15
- data/UPGRADING.md +23 -23
- data/graphlient.gemspec +19 -19
- data/lib/graphlient/adapters/http/adapter.rb +41 -41
- data/lib/graphlient/adapters/http/faraday_adapter.rb +79 -45
- data/lib/graphlient/adapters/http/http_adapter.rb +40 -39
- data/lib/graphlient/adapters/http.rb +3 -3
- data/lib/graphlient/adapters.rb +1 -1
- data/lib/graphlient/client.rb +98 -73
- data/lib/graphlient/errors/client_error.rb +6 -6
- data/lib/graphlient/errors/connection_failed_error.rb +6 -6
- data/lib/graphlient/errors/error.rb +13 -12
- data/lib/graphlient/errors/execution_error.rb +29 -29
- data/lib/graphlient/errors/faraday_server_error.rb +12 -12
- data/lib/graphlient/errors/graphql_error.rb +53 -52
- data/lib/graphlient/errors/http_options_error.rb +6 -6
- data/lib/graphlient/errors/http_server_error.rb +13 -13
- data/lib/graphlient/errors/server_error.rb +7 -7
- data/lib/graphlient/errors/timeout_error.rb +6 -6
- data/lib/graphlient/errors.rb +10 -10
- data/lib/graphlient/extensions/query.rb +15 -15
- data/lib/graphlient/extensions.rb +1 -1
- data/lib/graphlient/query/directive.rb +47 -0
- data/lib/graphlient/query/serializer/arguments.rb +67 -0
- data/lib/graphlient/query/serializer/directives.rb +17 -0
- data/lib/graphlient/query/serializer/evaluator.rb +15 -0
- data/lib/graphlient/query/serializer/fragments.rb +28 -0
- data/lib/graphlient/query/serializer/scalars.rb +63 -0
- data/lib/graphlient/query/serializer.rb +153 -0
- data/lib/graphlient/query.rb +29 -128
- data/lib/graphlient/schema.rb +27 -26
- data/lib/graphlient/version.rb +3 -3
- data/lib/graphlient.rb +8 -8
- data/spec/graphlient/adapters/http/faraday_adapter_spec.rb +172 -112
- data/spec/graphlient/adapters/http/http_adapter_spec.rb +60 -60
- data/spec/graphlient/client_dsl_spec.rb +153 -0
- data/spec/graphlient/client_query_spec.rb +369 -346
- data/spec/graphlient/client_schema_spec.rb +75 -55
- data/spec/graphlient/extensions/query_spec.rb +16 -16
- data/spec/graphlient/github_query_spec.rb +32 -32
- data/spec/graphlient/query_dsl_spec.rb +231 -0
- data/spec/graphlient/query_spec.rb +105 -105
- data/spec/graphlient/schema_spec.rb +56 -56
- data/spec/graphlient/static_client_query_spec.rb +75 -68
- data/spec/graphlient/webmock_client_query_spec.rb +41 -41
- data/spec/spec_helper.rb +26 -14
- data/spec/support/context/dummy_client.rb +33 -26
- data/spec/support/context/github_client.rb +18 -18
- data/spec/support/dummy_app.rb +21 -19
- data/spec/support/dummy_schema.rb +17 -17
- data/spec/support/fixtures/github/schema.yml +14282 -14282
- data/spec/support/fixtures/github/user.yml +76 -76
- data/spec/support/fixtures/github/viewer.yml +76 -76
- data/spec/support/fixtures/invoice_api.json +1288 -1288
- data/spec/support/mutations/create_invoice.rb +18 -18
- data/spec/support/queries/query.rb +48 -47
- data/spec/support/schema/github.json +44479 -44479
- data/spec/support/types/invoice_type.rb +13 -13
- data/spec/support/types/mutation_type.rb +5 -5
- data/spec/support/vcr.rb +9 -9
- metadata +13 -8
- data/Gemfile.danger +0 -5
data/README.md
CHANGED
|
@@ -1,508 +1,800 @@
|
|
|
1
|
-
# Graphlient
|
|
2
|
-
|
|
3
|
-
[](https://badge.fury.io/rb/graphlient)
|
|
4
|
-
[](https://github.com/ashkan18/graphlient/actions/workflows/ci.yml)
|
|
5
|
-
|
|
6
|
-
A friendlier Ruby client for consuming GraphQL-based APIs. Built on top of your usual [graphql-client](https://github.com/github/graphql-client), but with better defaults, more consistent error handling, and using the [faraday](https://github.com/lostisland/faraday) HTTP client.
|
|
7
|
-
|
|
8
|
-
# Table of Contents
|
|
9
|
-
|
|
10
|
-
- [Installation](#installation)
|
|
11
|
-
- [Usage](#usage)
|
|
12
|
-
- [Schema
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
response
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
```ruby
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
end
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
The
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
end
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1
|
+
# Graphlient
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/graphlient)
|
|
4
|
+
[](https://github.com/ashkan18/graphlient/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
A friendlier Ruby client for consuming GraphQL-based APIs. Built on top of your usual [graphql-client](https://github.com/github-community-projects/graphql-client), but with better defaults, more consistent error handling, and using the [faraday](https://github.com/lostisland/faraday) HTTP client.
|
|
7
|
+
|
|
8
|
+
# Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Usage](#usage)
|
|
12
|
+
- [Schema Storing and Loading on Disk](#schema-storing-and-loading-on-disk)
|
|
13
|
+
- [Preloading Schema Once](#preloading-schema-once)
|
|
14
|
+
- [Error Handling](#error-handling)
|
|
15
|
+
- [Executing Parameterized Queries and Mutations](#executing-parameterized-queries-and-mutations)
|
|
16
|
+
- [Parse and Execute Queries Separately](#parse-and-execute-queries-separately)
|
|
17
|
+
- [Build Query Strings without Validation](#build-query-strings-without-validation)
|
|
18
|
+
- [Dynamic vs. Static Queries](#dynamic-vs-static-queries)
|
|
19
|
+
- [Generate Queries with Graphlient::Query](#generate-queries-with-graphlientquery)
|
|
20
|
+
- [Fragment Spreads and Definitions in the DSL](#fragment-spreads-and-definitions-in-the-dsl)
|
|
21
|
+
- [Inline Fragments in the DSL](#inline-fragments-in-the-dsl)
|
|
22
|
+
- [Directives in the DSL](#directives-in-the-dsl)
|
|
23
|
+
- [Custom Scalar Types](#custom-scalar-types)
|
|
24
|
+
- [Create API Client Classes with Graphlient::Extension::Query](#create-api-client-classes-with-graphlientextensionquery)
|
|
25
|
+
- [Swapping the HTTP Stack](#swapping-the-http-stack)
|
|
26
|
+
- [Testing with Graphlient and RSpec](#testing-with-graphlient-and-rspec)
|
|
27
|
+
- [License](#license)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Add the following line to your Gemfile.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
gem 'graphlient'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Create a new instance of `Graphlient::Client` with a URL and optional headers/http_options.
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
client = Graphlient::Client.new('https://test-graphql.biz/graphql',
|
|
43
|
+
headers: {
|
|
44
|
+
'Authorization' => 'Bearer 123'
|
|
45
|
+
},
|
|
46
|
+
http_options: {
|
|
47
|
+
read_timeout: 20,
|
|
48
|
+
write_timeout: 30
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| http_options | default | type |
|
|
54
|
+
| ------------- | ------- | ------- |
|
|
55
|
+
| read_timeout | nil | seconds |
|
|
56
|
+
| write_timeout | nil | seconds |
|
|
57
|
+
|
|
58
|
+
The schema is available automatically via `.schema`.
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
client.schema # GraphQL::Schema
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Make queries with `query`, which takes a String or a block for the query definition.
|
|
65
|
+
|
|
66
|
+
With a String.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
response = client.query <<~GRAPHQL
|
|
70
|
+
query {
|
|
71
|
+
invoice(id: 10) {
|
|
72
|
+
id
|
|
73
|
+
total
|
|
74
|
+
line_items {
|
|
75
|
+
price
|
|
76
|
+
item_type
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
GRAPHQL
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
With a block.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
response = client.query do
|
|
87
|
+
query do
|
|
88
|
+
invoice(id: 10) do
|
|
89
|
+
id
|
|
90
|
+
total
|
|
91
|
+
line_items do
|
|
92
|
+
price
|
|
93
|
+
item_type
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This will call the endpoint setup in the configuration with `POST`, the `Authorization` header and `query` as follows.
|
|
101
|
+
|
|
102
|
+
```graphql
|
|
103
|
+
query {
|
|
104
|
+
invoice(id: 10) {
|
|
105
|
+
id
|
|
106
|
+
total
|
|
107
|
+
line_items {
|
|
108
|
+
price
|
|
109
|
+
item_type
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A successful response object always contains data which can be iterated upon. The following example returns the first line item's price.
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
response.data.invoice.line_items.first.price
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can also execute mutations the same way.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
response = client.query do
|
|
125
|
+
mutation do
|
|
126
|
+
createInvoice(input: { fee_in_cents: 12_345 }) do
|
|
127
|
+
id
|
|
128
|
+
fee_in_cents
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The successful response contains data in `response.data`. The following example returns the newly created invoice's ID.
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
response.data.create_invoice.first.id
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Schema storing and loading on disk
|
|
141
|
+
|
|
142
|
+
To reduce requests to graphql API you can cache schema:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
client = Client.new(url, schema_path: 'config/your_graphql_schema.json')
|
|
146
|
+
client.schema.dump! # you only need to call this when graphql schema changes
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Preloading Schema Once
|
|
150
|
+
|
|
151
|
+
Even if caching the schema on disk, instantiating `Graphlient::Client` often can be both time and memory intensive due to loading the schema for each instance. This is especially true if the schema is a large file. To get around these performance issues, instantiate your schema once and pass it in as a configuration option.
|
|
152
|
+
|
|
153
|
+
One time in an initializer
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
schema = Graphlient::Schema.new(
|
|
157
|
+
'https://graphql.foo.com/graphql', 'lib/graphql_schema_foo.json'
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Pass in each time you initialize a client
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
client = Graphlient::Client.new(
|
|
165
|
+
'https://graphql.foo.com/graphql',
|
|
166
|
+
schema: schema,
|
|
167
|
+
headers: {
|
|
168
|
+
'Authorization' => 'Bearer 123',
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Error Handling
|
|
174
|
+
|
|
175
|
+
Unlike graphql-client, Graphlient will always raise an exception unless the query has succeeded.
|
|
176
|
+
|
|
177
|
+
* [Graphlient::Errors::ClientError](lib/graphlient/errors/client_error.rb): all client-side query validation failures based on current schema
|
|
178
|
+
* [Graphlient::Errors::GraphQLError](lib/graphlient/errors/graphql_error.rb): all GraphQL API errors, with a humanly readable collection of problems
|
|
179
|
+
* [Graphlient::Errors::ExecutionError](lib/graphlient/errors/execution_error.rb): all GraphQL execution errors, with a humanly readable collection of problems
|
|
180
|
+
* [Graphlient::Errors::ServerError](lib/graphlient/errors/server_error.rb): all transport errors raised by HTTP Adapters. You can access `inner_exception`, `status_code` and `response` on these errors to get more details on what went wrong
|
|
181
|
+
* [Graphlient::Errors::FaradayServerError](lib/graphlient/errors/faraday_server_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these
|
|
182
|
+
* [Graphlient::Errors::HttpServerError](lib/graphlient/errors/http_server_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these
|
|
183
|
+
* [Graphlient::Errors::ConnectionFailedError](lib/graphlient/errors/connection_failed_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these
|
|
184
|
+
* [Graphlient::Errors::TimeoutError](lib/graphlient/errors/timeout_error.rb): all client-side timeouts raised by the Faraday adapter. This does not inherit from `ServerError` because no server response or status code is available; rescue `TimeoutError` separately
|
|
185
|
+
* [Graphlient::Errors::HttpOptionsError](lib/graphlient/errors/http_options_error.rb): all NoMethodError raised by HTTP Adapters when given options in `http_options` are invalid
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
All errors inherit from `Graphlient::Errors::Error` if you need to handle them in bulk.
|
|
189
|
+
|
|
190
|
+
### Executing Parameterized Queries and Mutations
|
|
191
|
+
|
|
192
|
+
Graphlient can execute parameterized queries and mutations by providing variables as query parameters.
|
|
193
|
+
|
|
194
|
+
The following query accepts an array of IDs.
|
|
195
|
+
|
|
196
|
+
With a String.
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
query = <<-GRAPHQL
|
|
200
|
+
query($ids: [Int]) {
|
|
201
|
+
invoices(ids: $ids) {
|
|
202
|
+
id
|
|
203
|
+
fee_in_cents
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
GRAPHQL
|
|
207
|
+
variables = { ids: [42] }
|
|
208
|
+
|
|
209
|
+
client.query(query, variables)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
With a block.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
client.query(ids: [42]) do
|
|
216
|
+
query(ids: [:int]) do
|
|
217
|
+
invoices(ids: :ids) do
|
|
218
|
+
id
|
|
219
|
+
fee_in_cents
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Graphlient supports following Scalar types for parameterized queries by default:
|
|
226
|
+
|
|
227
|
+
- `:id` maps to `ID`
|
|
228
|
+
- `:boolean` maps to `Boolean`
|
|
229
|
+
- `:float` maps to `Float`
|
|
230
|
+
- `:int` maps to `Int`
|
|
231
|
+
- `:string` maps to `String`
|
|
232
|
+
|
|
233
|
+
You can use any of the above types with `!` to make it required or use them in `[]` for array parameters.
|
|
234
|
+
|
|
235
|
+
For any other custom types, graphlient will simply use `to_s` of the symbol provided for the type, so `query(ids: [:InvoiceType!])` will result in `query($ids: [InvoiceType!])`.
|
|
236
|
+
|
|
237
|
+
The following mutation accepts a custom type that requires `fee_in_cents`.
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
client.query(input: { fee_in_cents: 12_345 }) do
|
|
241
|
+
mutation(input: :createInvoiceInput!) do
|
|
242
|
+
createInvoice(input: :input) do
|
|
243
|
+
id
|
|
244
|
+
fee_in_cents
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Parse and Execute Queries Separately
|
|
251
|
+
|
|
252
|
+
You can `parse` and `execute` queries separately with optional variables. This is highly recommended as parsing a query and validating a query on every request adds performance overhead. Parsing queries early allows validation errors to be discovered before request time and avoids many potential security issues.
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
# parse a query, returns a GraphQL::Client::OperationDefinition
|
|
256
|
+
query = client.parse do
|
|
257
|
+
query(ids: [:int]) do
|
|
258
|
+
invoices(ids: :ids) do
|
|
259
|
+
id
|
|
260
|
+
fee_in_cents
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# execute a query, returns a GraphQL::Client::Response
|
|
266
|
+
client.execute query, ids: [42]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Or pass in a string instead of a block:
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
# parse a query, returns a GraphQL::Client::OperationDefinition
|
|
273
|
+
query = client.parse <<~GRAPHQL
|
|
274
|
+
query($some_id: Int) {
|
|
275
|
+
invoice(id: $some_id) {
|
|
276
|
+
id
|
|
277
|
+
feeInCents
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
GRAPHQL
|
|
281
|
+
|
|
282
|
+
# execute a query, returns a GraphQL::Client::Response
|
|
283
|
+
client.execute query, ids: [42]
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Build Query Strings without Validation
|
|
287
|
+
|
|
288
|
+
`Client#to_query_string` serializes a DSL block into a GraphQL query string and
|
|
289
|
+
returns it as a plain `String`. It uses the same DSL serializer (`Graphlient::Query`)
|
|
290
|
+
that powers `client.query` and `client.parse`, but it stops there — no schema is
|
|
291
|
+
loaded, no graphql-client validation runs, no HTTP call is made.
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
client = Graphlient::Client.new('https://example.com/graphql',
|
|
295
|
+
headers: { 'Authorization' => 'Bearer 123' }
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
query_str = client.to_query_string do
|
|
299
|
+
query(id: :int) do
|
|
300
|
+
invoice(id: :id) do
|
|
301
|
+
id
|
|
302
|
+
feeInCents
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# => "query($id: Int){\n invoice(id: $id){\n id\n feeInCents\n }\n }"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
For fragment-free queries the string can be fed back to `client.execute`:
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
client.execute(query_str, id: 42)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Queries containing fragment spreads (`spread :Name` → `...Name`) cannot go back
|
|
317
|
+
through the gem — graphql-client requires fragments to be pre-registered module
|
|
318
|
+
constants, not named strings. Pass those directly to your own HTTP client instead.
|
|
319
|
+
|
|
320
|
+
This is also the escape hatch if you want to replace the graphql-client dependency
|
|
321
|
+
entirely. `to_query_string` gives you a standard GraphQL document — from there you
|
|
322
|
+
own the transport: Faraday, Net::HTTP, anything else. You get full control over
|
|
323
|
+
headers, retries, connection pooling, and middleware without any graphql-client
|
|
324
|
+
overhead.
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
conn = Faraday.new('https://example.com/graphql',
|
|
328
|
+
headers: { 'Authorization' => 'Bearer 123', 'Content-Type' => 'application/json' }
|
|
329
|
+
)
|
|
330
|
+
response = conn.post('/', { query: query_str, variables: { id: 42 } }.to_json)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Dynamic vs. Static Queries
|
|
334
|
+
|
|
335
|
+
Graphlient uses [graphql-client](https://github.com/github-community-projects/graphql-client), which [recommends](https://github.com/github-community-projects/graphql-client/blob/master/guides/dynamic-query-error.md) building queries as static module members along with dynamic variables during execution. This can be accomplished with graphlient the same way.
|
|
336
|
+
|
|
337
|
+
Create a new instance of `Graphlient::Client` with a URL and optional headers.
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
module SWAPI
|
|
341
|
+
Client = Graphlient::Client.new('https://test-graphql.biz/graphql',
|
|
342
|
+
headers: {
|
|
343
|
+
'Authorization' => 'Bearer 123'
|
|
344
|
+
},
|
|
345
|
+
allow_dynamic_queries: false
|
|
346
|
+
)
|
|
347
|
+
end
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The schema is available automatically via `.schema`.
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
SWAPI::Client.schema # GraphQL::Schema
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Define a query.
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
module SWAPI
|
|
360
|
+
InvoiceQuery = Client.parse do
|
|
361
|
+
query(id: :int) do
|
|
362
|
+
invoice(id: :id) do
|
|
363
|
+
id
|
|
364
|
+
fee_in_cents
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Execute the query.
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
response = SWAPI::Client.execute(SWAPI::InvoiceQuery, id: 42)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Note that in the example above the client is created with `allow_dynamic_queries: false` (only allow static queries), while graphlient defaults to `allow_dynamic_queries: true` (allow dynamic queries). This option is marked deprecated, but we're proposing to remove it and default it to `true` in [graphql-client#128](https://github.com/github-community-projects/graphql-client/issues/128).
|
|
378
|
+
|
|
379
|
+
### Generate Queries with Graphlient::Query
|
|
380
|
+
|
|
381
|
+
You can directly use `Graphlient::Query` to generate raw GraphQL queries.
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
query = Graphlient::Query.new do
|
|
385
|
+
query do
|
|
386
|
+
invoice(id: 10) do
|
|
387
|
+
line_items
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
query.to_s
|
|
393
|
+
# "\nquery {\n invoice(id: 10){\n line_items\n }\n }\n"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Fragment Spreads and Definitions in the DSL
|
|
397
|
+
|
|
398
|
+
Use `spread` to insert a named fragment spread (`...FragmentName`) in a DSL block:
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
client.query do
|
|
402
|
+
query do
|
|
403
|
+
invoice(id: 10) do
|
|
404
|
+
id
|
|
405
|
+
spread :InvoiceFields # → ...InvoiceFields
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Define the fragment body inline with `fragment` — graphlient assembles the complete
|
|
412
|
+
query string automatically, no external tooling needed:
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
415
|
+
client.query do
|
|
416
|
+
fragment(:InvoiceFields, on: :Invoice) do
|
|
417
|
+
id
|
|
418
|
+
feeInCents
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
query do
|
|
422
|
+
invoice(id: 10) do
|
|
423
|
+
spread :InvoiceFields
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Produces and sends:
|
|
430
|
+
|
|
431
|
+
```graphql
|
|
432
|
+
query {
|
|
433
|
+
invoice(id: 10) {
|
|
434
|
+
...InvoiceFields
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
fragment InvoiceFields on Invoice {
|
|
439
|
+
id
|
|
440
|
+
feeInCents
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Because the fragment is defined inline in the same query block, graphql-client treats it
|
|
445
|
+
as part of the same document — all fragment fields are directly accessible on the response
|
|
446
|
+
wrapper with no extra wrapping step:
|
|
447
|
+
|
|
448
|
+
```ruby
|
|
449
|
+
response = client.query do
|
|
450
|
+
fragment(:InvoiceFields, on: :Invoice) do
|
|
451
|
+
id
|
|
452
|
+
feeInCents
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
query do
|
|
456
|
+
invoice(id: 10) do
|
|
457
|
+
spread :InvoiceFields
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
response.data.invoice.id # 10
|
|
463
|
+
response.data.invoice.fee_in_cents # 20000
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Multiple fragments are supported. Fragments are scoped to the query call — no global
|
|
467
|
+
registry, no cross-contamination between requests.
|
|
468
|
+
|
|
469
|
+
You can also apply a directive to a spread (see [Directives in the DSL](#directives-in-the-dsl)):
|
|
470
|
+
|
|
471
|
+
```ruby
|
|
472
|
+
spread :InvoiceFields, _skip(if: :skip_invoice)
|
|
473
|
+
# → ...InvoiceFields @skip(if: $skip_invoice)
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Use of Fragments (graphql-client style)
|
|
477
|
+
|
|
478
|
+
[Fragments](https://github.com/github-community-projects/graphql-client#defining-queries) should be referred by constant:
|
|
479
|
+
|
|
480
|
+
```ruby
|
|
481
|
+
module Fragments
|
|
482
|
+
Invoice = client.parse <<~'GRAPHQL'
|
|
483
|
+
fragment on Invoice {
|
|
484
|
+
id
|
|
485
|
+
feeInCents
|
|
486
|
+
}
|
|
487
|
+
GRAPHQL
|
|
488
|
+
end
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`Graphlient` offers the syntax below to refer to the original constant:
|
|
492
|
+
* Triple underscore `___` to refer to the fragment
|
|
493
|
+
* Double underscore `__` for namespace separator
|
|
494
|
+
|
|
495
|
+
In this example, `Fragments::Invoice` would be referred as follows:
|
|
496
|
+
|
|
497
|
+
```ruby
|
|
498
|
+
invoice_query = client.parse do
|
|
499
|
+
query do
|
|
500
|
+
invoice(id: 10) do
|
|
501
|
+
id
|
|
502
|
+
___Fragments__Invoice
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
The wrapped response only allows access to fields that have been explicitly asked for.
|
|
509
|
+
In this example, while `id` has been referenced directly in the main query, `feeInCents`
|
|
510
|
+
has been spread via an **external fragment constant** and trying to access it in the
|
|
511
|
+
original wrapped response will throw
|
|
512
|
+
[`GraphQL::Client::ImplicitlyFetchedFieldError`](https://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md).
|
|
513
|
+
This is graphql-client's component-isolation mechanism: each fragment constant "owns" the
|
|
514
|
+
fields it declares, preventing accidental data access across component boundaries.
|
|
515
|
+
|
|
516
|
+
```ruby
|
|
517
|
+
response = client.execute(invoice_query)
|
|
518
|
+
result = response.data.invoice
|
|
519
|
+
result.to_h
|
|
520
|
+
# {"id" => 10, "feeInCents"=> 20000}
|
|
521
|
+
result.id
|
|
522
|
+
# 10
|
|
523
|
+
result.fee_in_cents
|
|
524
|
+
# raises GraphQL::Client::ImplicitlyFetchedFieldError
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
`feeInCents` cannot be fetched directly from the main query, but from the fragment as shown below:
|
|
528
|
+
|
|
529
|
+
```ruby
|
|
530
|
+
invoice = Fragments::Invoice.new(result)
|
|
531
|
+
invoice.id
|
|
532
|
+
# 10
|
|
533
|
+
invoice.fee_in_cents
|
|
534
|
+
# 20000
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
> **Note:** This component-isolation behaviour only applies to external fragment constants
|
|
538
|
+
> (the `___` / `__` pattern). Fragments defined inline via the `fragment` DSL in the same
|
|
539
|
+
> query block are not subject to this restriction — their fields are accessible directly
|
|
540
|
+
> on the operation response (see [Fragment Spreads and Definitions in the DSL](#fragment-spreads-and-definitions-in-the-dsl)).
|
|
541
|
+
|
|
542
|
+
### Inline Fragments in the DSL
|
|
543
|
+
|
|
544
|
+
Use `spread(on: :TypeName)` for inline fragments (`... on Type { }`), useful for union
|
|
545
|
+
types and interface implementations. It's the same `spread` verb as named fragment
|
|
546
|
+
spreads, and the same `on:` keyword as `fragment(name, on:)`:
|
|
547
|
+
|
|
548
|
+
```ruby
|
|
549
|
+
client.query do
|
|
550
|
+
query do
|
|
551
|
+
invoice(id: 10) do
|
|
552
|
+
spread(on: :PaidInvoice) do
|
|
553
|
+
amountPaid
|
|
554
|
+
end
|
|
555
|
+
spread(on: :UnpaidInvoice) do
|
|
556
|
+
amountDue
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Produces:
|
|
564
|
+
|
|
565
|
+
```graphql
|
|
566
|
+
query {
|
|
567
|
+
invoice(id: 10) {
|
|
568
|
+
... on PaidInvoice {
|
|
569
|
+
amountPaid
|
|
570
|
+
}
|
|
571
|
+
... on UnpaidInvoice {
|
|
572
|
+
amountDue
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Directives can be applied to inline fragments too (see [Directives in the DSL](#directives-in-the-dsl)):
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
spread(_skip(if: :skip_drafts), on: :DraftInvoice) { draftId }
|
|
582
|
+
# → ... on DraftInvoice @skip(if: $skip_drafts) { draftId }
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Directives in the DSL
|
|
586
|
+
|
|
587
|
+
Apply GraphQL directives to fields, spreads, and inline fragments using the `_name`
|
|
588
|
+
convention — any method starting with `_` followed by a lowercase letter is treated
|
|
589
|
+
as a directive (`_skip` → `@skip`, `_include` → `@include`, `_myDirective` → `@myDirective`).
|
|
590
|
+
|
|
591
|
+
**On a field:**
|
|
592
|
+
|
|
593
|
+
```ruby
|
|
594
|
+
client.query(some_id: :int, skip_fee: :boolean!) do
|
|
595
|
+
query(some_id: :int, skip_fee: :boolean!) do
|
|
596
|
+
invoice(id: :some_id) do
|
|
597
|
+
id
|
|
598
|
+
feeInCents _skip(if: :skip_fee) # → feeInCents @skip(if: $skip_fee)
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**On a fragment spread:**
|
|
605
|
+
|
|
606
|
+
```ruby
|
|
607
|
+
spread :InvoiceFields, _skip(if: :skip_invoice)
|
|
608
|
+
# → ...InvoiceFields @skip(if: $skip_invoice)
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**On an inline fragment:**
|
|
612
|
+
|
|
613
|
+
```ruby
|
|
614
|
+
spread(_skip(if: :skip_drafts), on: :DraftInvoice) { draftId }
|
|
615
|
+
# → ... on DraftInvoice @skip(if: $skip_drafts) { draftId }
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**Multiple directives on one field:**
|
|
619
|
+
|
|
620
|
+
```ruby
|
|
621
|
+
feeInCents _skip(if: :skip_fee), _include(if: :show_cents)
|
|
622
|
+
# → feeInCents @skip(if: $skip_fee) @include(if: $show_cents)
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
**No-argument directive:**
|
|
626
|
+
|
|
627
|
+
```ruby
|
|
628
|
+
legacyField _deprecated
|
|
629
|
+
# → legacyField @deprecated
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
The directive value is a plain Ruby method call that returns a `Directive` object
|
|
633
|
+
before the field method runs, ensuring the directive appears in the correct position
|
|
634
|
+
in the output string regardless of Ruby's evaluation order.
|
|
635
|
+
|
|
636
|
+
### Custom Scalar Types
|
|
637
|
+
|
|
638
|
+
By default, graphlient maps `:int → Int`, `:float → Float`, `:string → String`, and
|
|
639
|
+
`:boolean → Boolean` for variable type declarations. Register additional scalar types
|
|
640
|
+
in the client initialiser block:
|
|
641
|
+
|
|
642
|
+
```ruby
|
|
643
|
+
client = Graphlient::Client.new('https://example.com/graphql') do |c|
|
|
644
|
+
c.scalar :date, 'Date'
|
|
645
|
+
c.scalar :uuid, 'UUID'
|
|
646
|
+
c.scalar :decimal, 'Decimal'
|
|
647
|
+
end
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
Use the registered symbol in variable declarations:
|
|
651
|
+
|
|
652
|
+
```ruby
|
|
653
|
+
client.query(created_after: Date.today.iso8601, order_id: :uuid) do
|
|
654
|
+
query(created_after: :date, order_id: :uuid!) do
|
|
655
|
+
orders(created_after: :created_after, id: :order_id) do
|
|
656
|
+
id
|
|
657
|
+
total
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
# → query($created_after: Date, $order_id: UUID!) { ... }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Non-null variants work with `!`: `:date!` → `Date!`.
|
|
665
|
+
|
|
666
|
+
### Create API Client Classes with Graphlient::Extension::Query
|
|
667
|
+
|
|
668
|
+
You can include `Graphlient::Extensions::Query` in your class. This will add a new `method_missing` method to your context which will be used to generate GraphQL queries.
|
|
669
|
+
|
|
670
|
+
```ruby
|
|
671
|
+
include Graphlient::Extensions::Query
|
|
672
|
+
|
|
673
|
+
query = query do
|
|
674
|
+
invoice(id: 10) do
|
|
675
|
+
line_items
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
query.to_s
|
|
680
|
+
# "\nquery{\n invoice(id: 10){\n line_items\n }\n }\n"
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Swapping the HTTP Stack
|
|
684
|
+
|
|
685
|
+
You can swap the default Faraday adapter for `Net::HTTP`.
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
client = Graphlient::Client.new('https://test-graphql.biz/graphql',
|
|
689
|
+
http: Graphlient::Adapters::HTTP::HTTPAdapter
|
|
690
|
+
)
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Testing with Graphlient and RSpec
|
|
694
|
+
|
|
695
|
+
Use Graphlient inside your RSpec tests in a Rails application or with `Rack::Test` against your actual application.
|
|
696
|
+
|
|
697
|
+
```ruby
|
|
698
|
+
require 'spec_helper'
|
|
699
|
+
|
|
700
|
+
describe App do
|
|
701
|
+
include Rack::Test::Methods
|
|
702
|
+
|
|
703
|
+
def app
|
|
704
|
+
# ...
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
let(:client) do
|
|
708
|
+
Graphlient::Client.new('http://test-graphql.biz/graphql') do |client|
|
|
709
|
+
client.http do |h|
|
|
710
|
+
h.connection do |c|
|
|
711
|
+
c.adapter Faraday::Adapter::Rack, app
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
context 'an invoice' do
|
|
718
|
+
let(:result) do
|
|
719
|
+
client.query do
|
|
720
|
+
query do
|
|
721
|
+
invoice(id: 10) do
|
|
722
|
+
id
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
it 'can be retrieved' do
|
|
729
|
+
expect(result.data.invoice.id).to eq 10
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
Alternately you can `stub_request` with Webmock.
|
|
736
|
+
|
|
737
|
+
```ruby
|
|
738
|
+
describe App do
|
|
739
|
+
let(:url) { 'http://example.com/graphql' }
|
|
740
|
+
let(:client) { Graphlient::Client.new(url) }
|
|
741
|
+
|
|
742
|
+
before do
|
|
743
|
+
stub_request(:post, url).to_return(
|
|
744
|
+
status: 200,
|
|
745
|
+
body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json
|
|
746
|
+
)
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
it 'retrieves schema' do
|
|
750
|
+
expect(client.schema).to be_a Graphlient::Schema
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
In order to stub the response to actual queries, [dump the schema into a JSON file](#schema-storing-and-loading-on-disk) and specify it via schema_path as follows.
|
|
756
|
+
|
|
757
|
+
```ruby
|
|
758
|
+
describe App do
|
|
759
|
+
let(:url) { 'http://graph.biz/graphql' }
|
|
760
|
+
let(:client) { Graphlient::Client.new(url, schema_path: 'spec/support/fixtures/invoice_api.json') }
|
|
761
|
+
let(:query) do
|
|
762
|
+
<<~GRAPHQL
|
|
763
|
+
query{
|
|
764
|
+
invoice(id: 42) {
|
|
765
|
+
id
|
|
766
|
+
feeInCents
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
GRAPHQL
|
|
770
|
+
end
|
|
771
|
+
let(:json_response) do
|
|
772
|
+
{
|
|
773
|
+
'data' => {
|
|
774
|
+
'invoice' => {
|
|
775
|
+
'id' => '42',
|
|
776
|
+
'feeInCents' => 2000
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}.to_json
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
before do
|
|
783
|
+
stub_request(:post, url).to_return(
|
|
784
|
+
status: 200,
|
|
785
|
+
body: json_response
|
|
786
|
+
)
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
it 'returns invoice fees' do
|
|
790
|
+
response = client.query(query)
|
|
791
|
+
expect(response.data).to be_truthy
|
|
792
|
+
expect(response.data.invoice.id).to eq('42')
|
|
793
|
+
expect(response.data.invoice.fee_in_cents).to eq(2000)
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
## License
|
|
799
|
+
|
|
800
|
+
MIT License, see [LICENSE](LICENSE)
|