lighthouse-graphql 0.4.0 → 0.6.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/CHANGELOG.md +256 -1
- data/README.md +26 -15
- data/docs/caching.md +4 -3
- data/docs/configuration.md +16 -12
- data/docs/directives.md +294 -4
- data/docs/generators.md +84 -0
- data/docs/getting-started.md +93 -60
- data/docs/performance.md +2 -2
- data/docs/relationships.md +47 -4
- data/docs/relay.md +200 -0
- data/docs/resolvers.md +326 -34
- data/docs/security.md +73 -8
- data/docs/subscriptions.md +16 -9
- data/docs/types.md +218 -0
- data/docs/uploads.md +107 -0
- data/lib/generators/lighthouse/base_classes.rb +27 -49
- data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
- data/lib/generators/lighthouse/install/install_generator.rb +125 -45
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +10 -8
- data/lib/lighthouse/async_mutation_job.rb +17 -0
- data/lib/lighthouse/caching.rb +9 -3
- data/lib/lighthouse/complexity.rb +5 -4
- data/lib/lighthouse/config.rb +12 -18
- data/lib/lighthouse/controller.rb +53 -12
- data/lib/lighthouse/dataloader_sources/preload_loader.rb +16 -0
- data/lib/lighthouse/dataloader_sources/relation_counts_loader.rb +46 -0
- data/lib/lighthouse/default_resolve_dispatcher.rb +26 -0
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/argument_binding.rb +61 -0
- data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +220 -0
- data/lib/lighthouse/directive_resolvers/association_resolvers.rb +2 -2
- data/lib/lighthouse/directive_resolvers/async_resolver.rb +90 -0
- data/lib/lighthouse/directive_resolvers/base.rb +63 -14
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +89 -19
- data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
- data/lib/lighthouse/directive_resolvers/can_resolvers.rb +1 -1
- data/lib/lighthouse/directive_resolvers/convention_resolver.rb +2 -1
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/field_resolver.rb +35 -7
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +59 -0
- data/lib/lighthouse/directive_resolvers/lazy_load_resolver.rb +61 -0
- data/lib/lighthouse/directive_resolvers/limit_resolver.rb +59 -0
- data/lib/lighthouse/directive_resolvers/method_resolver.rb +38 -3
- data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/namespaced_resolver.rb +31 -0
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/resolution.rb +19 -4
- data/lib/lighthouse/directive_resolvers/subscription_resolver.rb +2 -1
- data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +136 -0
- data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +87 -0
- data/lib/lighthouse/directive_resolvers/with_count_resolver.rb +43 -0
- data/lib/lighthouse/directive_resolvers/with_resolver.rb +42 -0
- data/lib/lighthouse/directives/arguments/more_filters.rb +215 -0
- data/lib/lighthouse/directives/definitions.rb +52 -0
- data/lib/lighthouse/directives/model_directive.rb +131 -0
- data/lib/lighthouse/directives/node_directive.rb +134 -0
- data/lib/lighthouse/directives/pagination_manipulator.rb +1 -1
- data/lib/lighthouse/directives/registry.rb +21 -21
- data/lib/lighthouse/directives/visibility_directive.rb +71 -0
- data/lib/lighthouse/enum_directive.rb +41 -0
- data/lib/lighthouse/field_resolver_stack.rb +56 -6
- data/lib/lighthouse/generated_resolvers.rb +13 -8
- data/lib/lighthouse/global_id.rb +57 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
- data/lib/lighthouse/multipart_request.rb +43 -0
- data/lib/lighthouse/node_registry.rb +68 -0
- data/lib/lighthouse/pagination.rb +3 -0
- data/lib/lighthouse/rb_lighthouse.rb +34 -0
- data/lib/lighthouse/relation_counts.rb +29 -0
- data/lib/lighthouse/resolve_info.rb +80 -0
- data/lib/lighthouse/resolver_bases.rb +25 -4
- data/lib/lighthouse/resolver_dispatch.rb +51 -11
- data/lib/lighthouse/root_types.rb +117 -0
- data/lib/lighthouse/scalar_coercion.rb +72 -0
- data/lib/lighthouse/schema_cache.rb +2 -1
- data/lib/lighthouse/schema_generator.rb +13 -11
- data/lib/lighthouse/schema_implementation.rb +4 -8
- data/lib/lighthouse/support/environment.rb +13 -0
- data/lib/lighthouse/support/model_resolver.rb +7 -5
- data/lib/lighthouse/support/preload.rb +49 -0
- data/lib/lighthouse/type.rb +103 -0
- data/lib/lighthouse/type_resolution.rb +150 -0
- data/lib/lighthouse/upload.rb +28 -0
- data/lib/lighthouse/upload_store.rb +57 -0
- data/sig/lighthouse/directives.rbs +3 -2
- data/sig/lighthouse.rbs +10 -2
- metadata +40 -4
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -22
- data/lib/generators/lighthouse/mutation_generator.rb +0 -31
- data/lib/generators/lighthouse/query_generator.rb +0 -30
data/docs/directives.md
CHANGED
|
@@ -129,6 +129,7 @@ See [relationships.md](relationships.md) for the full guide.
|
|
|
129
129
|
| `@belongsTo` | inverse of has-one/has-many |
|
|
130
130
|
| `@belongsToMany` | many-to-many: a list, or paginated with `type:` |
|
|
131
131
|
| `@morphTo`, `@morphOne`, `@morphMany`, `@morphToMany` | the polymorphic variants |
|
|
132
|
+
| `@hasManyThrough`, `@hasOneThrough` | PHP Lighthouse's names for `@hasMany` / `@hasOne` on a `has_many` / `has_one :through` |
|
|
132
133
|
|
|
133
134
|
```graphql
|
|
134
135
|
type User {
|
|
@@ -142,6 +143,28 @@ type User {
|
|
|
142
143
|
The relation name defaults to the field name (snake_cased). Override with
|
|
143
144
|
`@hasMany(relation: "authored_posts")`.
|
|
144
145
|
|
|
146
|
+
### `@with`, `@withCount` and `@lazyLoad`
|
|
147
|
+
|
|
148
|
+
Eager loading for fields the relationship directives do not resolve, each across
|
|
149
|
+
every parent in the response at once. See
|
|
150
|
+
[relationships.md](relationships.md#eager-loading-for-other-fields).
|
|
151
|
+
|
|
152
|
+
```graphql
|
|
153
|
+
type User {
|
|
154
|
+
"@with loads the relation before the field resolves, for a method that reads it."
|
|
155
|
+
taskSummary: String! @with(relation: "tasks") @method(name: "task_summary")
|
|
156
|
+
|
|
157
|
+
"@withCount sets posts_count on the user; the field's resolver reads it."
|
|
158
|
+
activity: String! @withCount(relation: "posts", scopes: ["published"]) @method
|
|
159
|
+
|
|
160
|
+
"@lazyLoad loads relations of the records the field returns."
|
|
161
|
+
posts: [Post!]! @hasMany @lazyLoad(relations: ["tags", "comments.author"])
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`relation` / `relations` may be nested with dots. `scopes` narrow the loaded or
|
|
166
|
+
counted records. `@with`'s `relation` defaults to the field's name.
|
|
167
|
+
|
|
145
168
|
---
|
|
146
169
|
|
|
147
170
|
## Field directives
|
|
@@ -162,10 +185,16 @@ type Contact {
|
|
|
162
185
|
```
|
|
163
186
|
|
|
164
187
|
On `Query` and `Mutation` the name is looked up in the queries / mutations
|
|
165
|
-
namespaces,
|
|
188
|
+
namespaces, a nested name too (`Feeds::Home` is `Resolvers::Feeds::Home`), and
|
|
189
|
+
then as written. On any other type it is looked up in the resolvers namespace the
|
|
190
|
+
same way (`Posts::TagsCount` is `Resolvers::Posts::TagsCount`), then in
|
|
191
|
+
[`resolver_namespaces`](configuration.md). There, the class's `object` is the parent: the
|
|
192
|
+
record the field belongs to (see
|
|
193
|
+
[resolvers.md](resolvers.md#fields-of-other-types)). The class may
|
|
166
194
|
be a `GraphQL::Schema::Resolver` (graphql-ruby's contract), or a plain callable —
|
|
167
195
|
a class-level `call`, an instance `#call`, or an instance `#resolve`, each
|
|
168
|
-
receiving `(object, args, context)
|
|
196
|
+
receiving `(object, args, context)`, and the [`ResolveInfo`](resolvers.md#resolveinfo)
|
|
197
|
+
as a fourth argument when it takes one:
|
|
169
198
|
|
|
170
199
|
```ruby
|
|
171
200
|
module Resolvers
|
|
@@ -177,6 +206,17 @@ module Resolvers
|
|
|
177
206
|
end
|
|
178
207
|
```
|
|
179
208
|
|
|
209
|
+
A method after `@` replaces `call`, as PHP Lighthouse's `Class@method`: it is called
|
|
210
|
+
on the class when the class has it, else on an instance. On a
|
|
211
|
+
`GraphQL::Schema::Resolver` it replaces `resolve`, and `ready?` / `authorized?`
|
|
212
|
+
still run.
|
|
213
|
+
|
|
214
|
+
```graphql
|
|
215
|
+
type Query {
|
|
216
|
+
usersTotal: Int @field(resolver: "Statistics@users_total") # Resolvers::Statistics.users_total
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
180
220
|
### `@rename`
|
|
181
221
|
|
|
182
222
|
Read a differently-named attribute off the parent object. This is the explicit
|
|
@@ -191,15 +231,164 @@ type User {
|
|
|
191
231
|
### `@method`
|
|
192
232
|
|
|
193
233
|
Resolve a field by calling a method on the parent object (PHP-parity). Great for
|
|
194
|
-
exposing a computed method or predicate without a custom resolver.
|
|
234
|
+
exposing a computed method or predicate without a custom resolver. `name:`
|
|
235
|
+
defaults to the field's snake_case name.
|
|
195
236
|
|
|
196
237
|
```graphql
|
|
197
238
|
type Conversation {
|
|
198
239
|
labels: [String!]! @method(name: "label_list")
|
|
199
240
|
canReply: Boolean @method(name: "can_reply?")
|
|
241
|
+
summary(words: Int = 20): String! @method
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The field's arguments reach the method: as snake_case keywords when it takes
|
|
246
|
+
keywords (`def summary(words:)`), otherwise positionally, in the order the SDL
|
|
247
|
+
declares them, as PHP Lighthouse passes them (`def summary(words)`). The SDL's
|
|
248
|
+
defaults apply, and a method taking no parameters is called without them.
|
|
249
|
+
|
|
250
|
+
## Relay
|
|
251
|
+
|
|
252
|
+
### `@globalId` and `@node`
|
|
253
|
+
|
|
254
|
+
`@globalId` encodes a field's value as a Relay global id (base64 of `Type:id`), and
|
|
255
|
+
decodes an argument or input field (`decode: ARRAY | TYPE | ID`). `@node` on a type
|
|
256
|
+
makes it implement `Node` and adds `Query.node(id:)`, which finds the type's model
|
|
257
|
+
record, or calls `@node(resolver:)`. See [relay.md](relay.md).
|
|
258
|
+
|
|
259
|
+
```graphql
|
|
260
|
+
type User @node {
|
|
261
|
+
id: ID! @globalId
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
type Query {
|
|
265
|
+
user(id: ID! @globalId(decode: ID) @eq): User @find
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Execution
|
|
270
|
+
|
|
271
|
+
### `@namespaced`
|
|
272
|
+
|
|
273
|
+
A field that only groups others: it resolves to `true`, and the fields of its type
|
|
274
|
+
resolve with their own directives. On `Query` or `Mutation`.
|
|
275
|
+
|
|
276
|
+
```graphql
|
|
277
|
+
type Query { post: PostQueries! @namespaced }
|
|
278
|
+
type PostQueries {
|
|
279
|
+
find(id: ID! @eq): Post @find
|
|
280
|
+
list: [Post!]! @paginate
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### `@throttle`
|
|
285
|
+
|
|
286
|
+
Rails' `rate_limit` on one field: at most `maxAttempts` calls (its `to:`) every
|
|
287
|
+
`decayMinutes` (its `within:`, default 60 a minute), counted before anything else runs.
|
|
288
|
+
It takes `rate_limit`'s options, and counts as `rate_limit` does: in the controller's
|
|
289
|
+
`cache_store`, under the same key, and instrumented as `rate_limit.action_controller`.
|
|
290
|
+
See [security.md](security.md#rate-limiting).
|
|
291
|
+
|
|
292
|
+
| Argument | `rate_limit`'s | Default |
|
|
293
|
+
| --- | --- | --- |
|
|
294
|
+
| `by:` | `by:`, who the client is: a path in the context, as [`@inject`](resolvers.md#spread-and-inject)'s (`"current_user.id"`) | the client's IP |
|
|
295
|
+
| `with:` | `with:`, the controller's method to call past the limit | `rate_limit`'s: a 429 (it raises `ActionController::TooManyRequests`) |
|
|
296
|
+
| `name:` | `name:`; fields, and the controller's own `rate_limit`, of the same name count together | the field (`Query.x`) |
|
|
297
|
+
| `prefix:` | `scope:` | the controller's path |
|
|
298
|
+
|
|
299
|
+
```graphql
|
|
300
|
+
type Mutation {
|
|
301
|
+
login(email: String!, password: String!): User @throttle(maxAttempts: 5)
|
|
302
|
+
createTag(name: String!): Tag! @throttle(maxAttempts: 20, by: "current_user.id", with: "too_many_requests") @guard @create
|
|
200
303
|
}
|
|
201
304
|
```
|
|
202
305
|
|
|
306
|
+
### `@async`
|
|
307
|
+
|
|
308
|
+
Runs a mutation in a job and answers `true` at once. `@guard`, the `@can*` family,
|
|
309
|
+
`@validator` and the argument directives run in the request, so the client still
|
|
310
|
+
gets their errors; what resolves the field runs in
|
|
311
|
+
`Lighthouse::GraphQL::AsyncMutationJob` (ActiveJob), with the arguments and the
|
|
312
|
+
context values ActiveJob can serialize (records too, through GlobalID). The field
|
|
313
|
+
must be a `Mutation` field returning `Boolean`, and the schema a named constant.
|
|
314
|
+
|
|
315
|
+
```graphql
|
|
316
|
+
type Mutation {
|
|
317
|
+
importFeed(url: String!): Boolean! @guard @async(queue: "imports")
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `@hide` and `@show`
|
|
322
|
+
|
|
323
|
+
Leave a field out of the schema by environment (`Rails.env`): `@hide(env:)` removes
|
|
324
|
+
it in the environments listed, `@show(env:)` keeps it only in those.
|
|
325
|
+
|
|
326
|
+
```graphql
|
|
327
|
+
type Query {
|
|
328
|
+
debugInfo: String @show(env: ["development", "test"])
|
|
329
|
+
legacyFeed: [Post!]! @hide(env: ["production"]) @all
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Type directives
|
|
334
|
+
|
|
335
|
+
### `@model`
|
|
336
|
+
|
|
337
|
+
Maps an object type to the ActiveRecord model whose records it resolves, when the
|
|
338
|
+
names differ, as PHP Lighthouse's. Every directive that takes `model:` (`@all`,
|
|
339
|
+
`@find`, `@first`, `@paginate`, `@create`, `@update`, `@upsert`, `@delete`, the
|
|
340
|
+
`@can*` family...) on a field returning the type gets it, unless it names its own; a
|
|
341
|
+
union or interface resolves the model's records to the type; `@node` finds them. The
|
|
342
|
+
schema does not build unless the class is an ActiveRecord model.
|
|
343
|
+
|
|
344
|
+
```graphql
|
|
345
|
+
type Article @model(class: "Post") { id: ID! title: String! }
|
|
346
|
+
|
|
347
|
+
type Query {
|
|
348
|
+
articles: [Article!]! @paginate # pages through Post
|
|
349
|
+
article(id: ID! @eq): Article @find
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### `@enum`
|
|
354
|
+
|
|
355
|
+
The internal value of an enum value: what a resolver gets for it in an argument,
|
|
356
|
+
and what a result answers with it for.
|
|
357
|
+
|
|
358
|
+
```graphql
|
|
359
|
+
enum Role {
|
|
360
|
+
ADMIN @enum(value: "admin")
|
|
361
|
+
MEMBER @enum(value: "member")
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
type Query { users(role: Role @eq): [User!]! @all } # filters on "admin"
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
They go on a union, an interface or a scalar, and name the class that resolves or
|
|
368
|
+
coerces its values. Without them, Lighthouse uses the type's class
|
|
369
|
+
(`Types::<Name>Type` including `Lighthouse::GraphQL::Type`), or its default. See
|
|
370
|
+
[types.md](types.md).
|
|
371
|
+
|
|
372
|
+
### `@union` and `@interface`
|
|
373
|
+
|
|
374
|
+
Name the class that picks the object type of each value: `resolve_type(object,
|
|
375
|
+
context)` or `call(object, context)`, returning a type name or a type class.
|
|
376
|
+
Looked up in the types namespace (nested names too), then as written.
|
|
377
|
+
|
|
378
|
+
```graphql
|
|
379
|
+
union SearchResult @union(resolveType: "SearchResultResolver") = Post | User
|
|
380
|
+
interface Authored @interface(resolveType: "AuthoredResolver") { id: ID! }
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### `@scalar`
|
|
384
|
+
|
|
385
|
+
Names the class that reads and writes the scalar's values: `coerce_input(value,
|
|
386
|
+
context)` and `coerce_result(value, context)`, as a `GraphQL::Schema::Scalar`.
|
|
387
|
+
|
|
388
|
+
```graphql
|
|
389
|
+
scalar DateTime @scalar(class: "Types::DateTimeType")
|
|
390
|
+
```
|
|
391
|
+
|
|
203
392
|
---
|
|
204
393
|
|
|
205
394
|
## Authentication & Authorization
|
|
@@ -263,7 +452,40 @@ type Query {
|
|
|
263
452
|
```
|
|
264
453
|
|
|
265
454
|
Builders are looked up in their own namespace, `Builders::` (app/graphql/builders)
|
|
266
|
-
— they are not resolvers. See [resolvers.md](resolvers.md#builders).
|
|
455
|
+
— they are not resolvers. See [resolvers.md](resolvers.md#builders). The class is
|
|
456
|
+
`class:` with `method:` (default `call`), or PHP Lighthouse's `method: "Class@method"`.
|
|
457
|
+
|
|
458
|
+
On a field, the method gets `(relation, args, context)`, or the directive's
|
|
459
|
+
`value:` in place of the arguments. On an argument or an input field, it gets
|
|
460
|
+
`(relation, value, context)` whenever the client sends that argument, an explicit
|
|
461
|
+
`null` included, and not when it is left out:
|
|
462
|
+
|
|
463
|
+
```graphql
|
|
464
|
+
type Query {
|
|
465
|
+
posts(since: DateTime @builder(method: "Recent@since")): [Post!]! @all
|
|
466
|
+
latest: [Post!]! @all @builder(class: "Recent", value: 5)
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
```ruby
|
|
471
|
+
class Builders::Recent
|
|
472
|
+
def self.call(relation, count, _context) = relation.order(created_at: :desc).limit(count)
|
|
473
|
+
def self.since(relation, time, _context) = time ? relation.where(created_at: time..) : relation
|
|
474
|
+
end
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### `@whereAuth`
|
|
478
|
+
|
|
479
|
+
Keep only the records the signed-in user owns, through a relation of the model: a
|
|
480
|
+
`belongs_to`, a `has_many`, a `:through`. A guest gets none. It applies where
|
|
481
|
+
`@builder` does (`@all`, `@paginate`, `@first`, `@count`, `enhance_builder`).
|
|
482
|
+
|
|
483
|
+
```graphql
|
|
484
|
+
type Query {
|
|
485
|
+
myPosts: [Post!]! @paginate @whereAuth(relation: "author")
|
|
486
|
+
myTeams: [Team!]! @all @whereAuth(relation: "members")
|
|
487
|
+
}
|
|
488
|
+
```
|
|
267
489
|
|
|
268
490
|
```ruby
|
|
269
491
|
module Builders
|
|
@@ -292,6 +514,10 @@ Full guide: [filtering-and-ordering.md](filtering-and-ordering.md).
|
|
|
292
514
|
| `@like` | `column LIKE %value%` |
|
|
293
515
|
| `@whereKey` | `primary key = value` (`IN` for a list) |
|
|
294
516
|
| `@where(operator:)` | generic operator (`GT`, `GTE`, `IN`, `IS_NULL`, `CONTAINS`, …) |
|
|
517
|
+
| `@whereNull` / `@whereNotNull` | `true`: `column IS NULL` / `IS NOT NULL`; `false`: the opposite |
|
|
518
|
+
| `@whereBetween` / `@whereNotBetween` | `column BETWEEN` / `NOT BETWEEN` the input's two fields, in order |
|
|
519
|
+
| `@whereJsonContains` | a JSON column contains the value (every value of a list); `key: "meta->tags"` points into it |
|
|
520
|
+
| `@scope(name:)` | calls the model scope with the value (`name:` defaults to the argument's) |
|
|
295
521
|
| `@orderBy` | sort by one or more columns |
|
|
296
522
|
|
|
297
523
|
```graphql
|
|
@@ -307,6 +533,41 @@ type Query {
|
|
|
307
533
|
|
|
308
534
|
The column defaults to the snake_cased argument name; override with `key:`.
|
|
309
535
|
|
|
536
|
+
```graphql
|
|
537
|
+
type Query {
|
|
538
|
+
posts(
|
|
539
|
+
unpublished: Boolean @whereNull(key: "published_at")
|
|
540
|
+
createdAt: DateRange @whereBetween(key: "created_at")
|
|
541
|
+
tagged: [String!] @whereJsonContains(key: "tags")
|
|
542
|
+
trending: Boolean @scope
|
|
543
|
+
filter: PostFilter @nest
|
|
544
|
+
limit: Int @limit
|
|
545
|
+
): [Post!]! @all
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
input DateRange { from: DateTime! to: DateTime! }
|
|
549
|
+
input PostFilter { title: String @like, authorId: ID @eq(key: "author_id") }
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### `@nest`
|
|
553
|
+
|
|
554
|
+
On an argument whose type is an input object, applies the filters of the input's
|
|
555
|
+
fields (at any depth, for those that carry `@nest` too), as if they were the
|
|
556
|
+
field's own arguments.
|
|
557
|
+
|
|
558
|
+
### `@limit`
|
|
559
|
+
|
|
560
|
+
On an `Int` argument, caps how many results the field returns at the client's
|
|
561
|
+
value; `null` leaves them alone. A query not run yet gets a `LIMIT`; the records of
|
|
562
|
+
a relation, loaded for every parent at once, are cut per parent. A negative value is
|
|
563
|
+
an error.
|
|
564
|
+
|
|
565
|
+
`@limit(builder: true)` puts the `LIMIT` on the field's query instead, with the
|
|
566
|
+
other argument directives. Use it on root fields: on a relation batched for many
|
|
567
|
+
parents it would cut them all together, as PHP Lighthouse warns. PHP Lighthouse
|
|
568
|
+
also allows `@limit` on the field itself, where it only marks the field the argument
|
|
569
|
+
limits; it is accepted there and changes nothing.
|
|
570
|
+
|
|
310
571
|
### `@whereConditions` / `@whereHasConditions`
|
|
311
572
|
|
|
312
573
|
Give clients a structured, dynamic `WHERE` builder (AND/OR/operators, and HAS for
|
|
@@ -319,6 +580,35 @@ type Query {
|
|
|
319
580
|
}
|
|
320
581
|
```
|
|
321
582
|
|
|
583
|
+
### `@trim`, `@hash`, `@drop`, `@bind` and `@upload`
|
|
584
|
+
|
|
585
|
+
Rewrite an argument's or input field's value before the field resolves, in the
|
|
586
|
+
order the SDL lists them:
|
|
587
|
+
|
|
588
|
+
| Directive | The resolver gets |
|
|
589
|
+
| --- | --- |
|
|
590
|
+
| `@trim` | the string without leading and trailing whitespace. On a field: every string it takes, nested inputs included |
|
|
591
|
+
| `@drop` | nothing: the argument is removed |
|
|
592
|
+
| `@hash` | the value hashed with BCrypt (the `bcrypt` gem, as `has_secure_password`) |
|
|
593
|
+
| `@bind(class:, column: "id", with: [], optional: false)` | the model record(s) the value identifies, eager-loading `with`; or what a class's `call(value, context)` returns |
|
|
594
|
+
| `@upload(disk:, path: "/", public: false)` | the signed id of the ActiveStorage blob the file was stored as, on the app's service `disk` names ([uploads.md](uploads.md)) |
|
|
595
|
+
|
|
596
|
+
```graphql
|
|
597
|
+
type Mutation {
|
|
598
|
+
signUp(email: String! @trim, password: String! @hash, captcha: String @drop): User! @create
|
|
599
|
+
assign(task: ID! @bind(class: "Task"), user: ID! @bind(class: "User", with: ["roles"])): Task!
|
|
600
|
+
updateAvatar(avatar: Upload! @upload(path: "avatars")): User! @update @inject(context: "current_user.id", name: "id")
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
`@bind` fails the field with a validation error on the argument (`The selected task
|
|
605
|
+
is invalid.`) when no record matches, unless `optional: true`, which passes `null`.
|
|
606
|
+
|
|
607
|
+
`@globalId`, `@trim` and `@drop` only rewrite values: they run first, so `@guard`,
|
|
608
|
+
the `@can*` family and `@validator` see the result. `@hash`, `@bind` and `@upload`
|
|
609
|
+
run once those have let the call through: a validator sees the password before it
|
|
610
|
+
is hashed, and nothing is stored or looked up for a refused call.
|
|
611
|
+
|
|
322
612
|
### `@convertEmptyStringsToNull`
|
|
323
613
|
|
|
324
614
|
Replaces incoming empty strings `""` with `null`, before any resolver sees them —
|
data/docs/generators.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Generators
|
|
2
|
+
|
|
3
|
+
Lighthouse builds on graphql-ruby, and so do its generators: it writes only what
|
|
4
|
+
graphql-ruby has no generator for, or what an SDL-first schema needs differently.
|
|
5
|
+
Every graphql-ruby app's schema class and base classes (`Types::BaseObject`,
|
|
6
|
+
`Resolvers::BaseResolver`, `Mutations::BaseMutation`, ...) come from graphql-ruby's
|
|
7
|
+
own `graphql:install`, and `lighthouse:install` builds on them.
|
|
8
|
+
|
|
9
|
+
## `lighthouse:install`
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
bin/rails generate graphql:install # graphql-ruby's, with the options you choose
|
|
13
|
+
bin/rails generate lighthouse:install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
It works on what graphql-ruby's `graphql:install` wrote, with whichever of its
|
|
17
|
+
options the app chose (`--relay`, the default, `--batch`, `--skip-mutation-root-type`,
|
|
18
|
+
`--schema`, `--directory`, `--api`, ...). When the app has no graphql-ruby schema
|
|
19
|
+
class yet, it runs `graphql:install` first, with graphql-ruby's defaults (and
|
|
20
|
+
`--api` when `config.api_only = true`). Then it adapts what graphql-ruby wrote:
|
|
21
|
+
|
|
22
|
+
| File | What Lighthouse does |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `app/graphql/app_schema.rb` | `class AppSchema < GraphQL::Schema` becomes `class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(options: { base_types: { ... } })`, naming every class graphql-ruby wrote. The rest of the class stays, but the `query(...)` / `mutation(...)` lines (the SDL's root types are built on those classes) and the `resolve_type` stub (Lighthouse resolves types). With `--batch`, `use GraphQL::Dataloader` goes beside `use GraphQL::Batch`, as Lighthouse's directives batch through the Dataloader |
|
|
25
|
+
| `app/graphql/types/query_type.rb`, `mutation_type.rb` | The example `test_field` goes. Relay's `node` / `nodes` stay: the SDL's `Query` is built on `Types::QueryType`, so its `field`s are the schema's too |
|
|
26
|
+
| `app/graphql/types/base_object.rb`, `base_interface.rb`, `base_union.rb`, `base_scalar.rb` | `include Lighthouse::GraphQL::Type`: every type class inheriting from them is Lighthouse's, `Types::QueryType` and `Types::NodeType` included |
|
|
27
|
+
| `app/graphql/mutations/base_mutation.rb` | Kept as graphql-ruby wrote it, a `RelayClassicMutation` (see [resolvers.md](resolvers.md#resolution-by-convention)) |
|
|
28
|
+
| `app/controllers/graphql_controller.rb` | Replaced by one including `Lighthouse::GraphQL::Controller` (`lighthouse_schema AppSchema`), when it is still as graphql-ruby wrote it. One the app changed is kept, and the install says how to serve the schema |
|
|
29
|
+
|
|
30
|
+
And it adds Lighthouse's own:
|
|
31
|
+
|
|
32
|
+
| File | What it is |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `config/initializers/lighthouse.rb` | Every [configuration](configuration.md) option, commented out |
|
|
35
|
+
| `graphql/schema.graphql` | A starter schema, unless the folder holds `.graphql` files already |
|
|
36
|
+
| `graphql.config.yml` | IDE configuration, and `.gitignore` entries for the [IDE helper](getting-started.md#ide-support) files |
|
|
37
|
+
| `app/graphql/subscriptions/base_subscription.rb` | With `--subscriptions` only: graphql-ruby has no subscription base. It includes `Lighthouse::GraphQL::Subscription` |
|
|
38
|
+
| `config/routes.rb` | `post "/graphql", to: "graphql#execute"`, when missing |
|
|
39
|
+
|
|
40
|
+
| Option | Effect |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `--schema=ApiSchema` | Which graphql-ruby schema class to adopt, and its name when `graphql:install` has to run |
|
|
43
|
+
| `--sdl-folder=app/schema` | Where the `.graphql` files live (default `graphql`); sets `config.sdl_folder` |
|
|
44
|
+
| `--subscriptions` | Write `Subscriptions::BaseSubscription` and name it in `base_types` |
|
|
45
|
+
| `--skip-controller` | Leave graphql-ruby's controller as it is, and add no route — for apps that serve GraphQL themselves |
|
|
46
|
+
| `--skip-ide` | No `graphql.config.yml` or `.gitignore` entries |
|
|
47
|
+
|
|
48
|
+
Rerunning it changes nothing: a schema class that is Lighthouse's already, a file
|
|
49
|
+
the app has, the route and the `.gitignore` entries are left as they are.
|
|
50
|
+
|
|
51
|
+
## Lighthouse's generators
|
|
52
|
+
|
|
53
|
+
For the classes graphql-ruby has no generator for, or none that fits an SDL-first
|
|
54
|
+
schema. Each writes into the first namespace configured for its kind (or
|
|
55
|
+
`--namespace=…`); namespaced names nest modules (`Blog::Publish`).
|
|
56
|
+
|
|
57
|
+
| Generator | Writes |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `lighthouse:subscription PostUpdated` | `Subscriptions::PostUpdated < BaseSubscription`, with its hooks (and the base, when missing) |
|
|
60
|
+
| `lighthouse:builder Published` | `Builders::Published`, for `@builder(class: "Published")` |
|
|
61
|
+
| `lighthouse:validator CreatePostInput` | `Validators::CreatePostInputValidator`, a `Dry::Validation::Contract` |
|
|
62
|
+
|
|
63
|
+
A subscription's arguments and return type are the SDL's, so the class declares
|
|
64
|
+
neither. When `Subscriptions::BaseSubscription` is missing, `lighthouse:subscription`
|
|
65
|
+
writes it.
|
|
66
|
+
|
|
67
|
+
A resolver or mutation class has no Lighthouse generator: write a
|
|
68
|
+
`Resolvers::BaseResolver` subclass by hand (see [resolvers.md](resolvers.md)), add a
|
|
69
|
+
method to `Types::QueryType`, or run graphql-ruby's `graphql:mutation`.
|
|
70
|
+
|
|
71
|
+
## graphql-ruby's generators in a Lighthouse app
|
|
72
|
+
|
|
73
|
+
| Generator | In a Lighthouse app |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `graphql:install` | The base `lighthouse:install` builds on: run it first with the options you want, or let `lighthouse:install` run it with its defaults |
|
|
76
|
+
| `graphql:object Post` | **Use it** for a [type class](resolvers.md#type-classes): it inherits from `Types::BaseObject`, so it is Lighthouse's; add methods. The `field` lines it writes (from the model's columns, when there is one) are ignored, as the SDL declares the fields: delete them |
|
|
77
|
+
| `graphql:scalar DateTime` | **Use it** for a [scalar](types.md#scalars): its `coerce_input` / `coerce_result` are the ones Lighthouse calls |
|
|
78
|
+
| `graphql:union SearchResult`, `graphql:interface Authored` | **Use them** when the [default resolution](types.md#the-default) is not enough: add a `resolve_type`. Their `possible_types` and `field` lines are ignored |
|
|
79
|
+
| `graphql:enum`, `graphql:input` | Not needed: the SDL declares enums and inputs ([`@enum`](directives.md#enum) gives a value an internal one) |
|
|
80
|
+
| `graphql:mutation PublishPost` | **Use it** for a code-first mutation: it writes `Mutations::PublishPost` and adds `field :publish_post, mutation: Mutations::PublishPost` to `Types::MutationType`, which the SDL's `Mutation` is built on. The class declares its own arguments and fields, as in graphql-ruby |
|
|
81
|
+
| `graphql:mutation_create` / `_update` / `_delete` | Code-first mutations like `graphql:mutation`'s, whose payload and input are code-first types (`Types::PostType`, `Types::PostInputType`). For a type the SDL declares, use [`@create`, `@update`, `@delete`](mutations.md) |
|
|
82
|
+
| `graphql:relay` | Run by `graphql:install` (`--relay` is its default). On an app installed with `--no-relay`, it adds `Types::NodeType`, `node` / `nodes` and the schema's `id_from_object` / `object_from_id`: add `node: Types::NodeType` to `base_types` by hand. See [relay.md](relay.md#relay-through-graphql-ruby) |
|
|
83
|
+
| `graphql:loader` | A GraphQL::Batch loader, which your own resolver classes may use; Lighthouse's directives batch through graphql-ruby's Dataloader instead |
|
|
84
|
+
| `graphql:detailed_trace` | **Not ready**: it looks for `class AppSchema < GraphQL::Schema`, which a Lighthouse schema class (`< Lighthouse::GraphQL::RbLightHouse.get_schema(...)`) does not match, and stops. Add `use GraphQL::Tracing::DetailedTrace` and `def self.detailed_trace?(query)` to the class body by hand, with graphql-ruby's migration (or `redis:`) |
|
data/docs/getting-started.md
CHANGED
|
@@ -24,32 +24,27 @@ a hard dependency of Lighthouse so non-federated apps stay lean.
|
|
|
24
24
|
bin/rails generate lighthouse:install
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
does steps 1–5 below for you
|
|
27
|
+
does steps 1–5 below for you. It builds on graphql-ruby's own install,
|
|
28
|
+
`rails g graphql:install`, with whichever of its options the app chose (`--relay`,
|
|
29
|
+
`--batch`, `--api`, ...). When the app has no graphql-ruby schema class yet, it runs
|
|
30
|
+
`graphql:install` first, with graphql-ruby's defaults. Then it adapts what
|
|
31
|
+
graphql-ruby wrote, and adds Lighthouse's own:
|
|
28
32
|
|
|
29
33
|
| File | What it is |
|
|
30
34
|
| --- | --- |
|
|
35
|
+
| `app/graphql/app_schema.rb` | graphql-ruby's schema class, now built by Lighthouse from the SDL on every class graphql-ruby wrote (`base_types`). The rest of the class is graphql-ruby's |
|
|
36
|
+
| `app/graphql/types/query_type.rb`, `mutation_type.rb` | graphql-ruby's root types, which the SDL's `Query` and `Mutation` are built on. Their example `test_field` goes; Relay's `node` / `nodes` stay |
|
|
37
|
+
| `app/graphql/types/base_*.rb`, `resolvers/base_resolver.rb`, `mutations/base_mutation.rb` | graphql-ruby's base classes. `BaseObject`, `BaseInterface`, `BaseUnion` and `BaseScalar` include `Lighthouse::GraphQL::Type` |
|
|
38
|
+
| `app/controllers/graphql_controller.rb` | graphql-ruby's controller, replaced by one including `Lighthouse::GraphQL::Controller` |
|
|
31
39
|
| `config/initializers/lighthouse.rb` | Every [configuration](configuration.md) option, commented out at its default |
|
|
32
40
|
| `graphql/schema.graphql` | A starter schema (`user` via `@find`, `users` via `@paginate`) that assumes a `User` model — skipped when the folder already holds `.graphql` files |
|
|
33
|
-
| `app/graphql/app_schema.rb` | The executable schema, with `GraphQL::Dataloader` enabled |
|
|
34
|
-
| `app/controllers/graphql_controller.rb` | The endpoint, plus a `post '/graphql'` route |
|
|
35
41
|
| `graphql.config.yml` | IDE configuration — see [IDE support](#ide-support) |
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| `--skip-ide` | No `graphql.config.yml` or `.gitignore` entries |
|
|
43
|
-
|
|
44
|
-
Rerunning it is safe: the route and `.gitignore` entries are only added once, and
|
|
45
|
-
an existing schema class or controller is kept (pass `--force` to replace it).
|
|
46
|
-
|
|
47
|
-
It coexists with graphql-ruby's own tooling. The generators live under
|
|
48
|
-
`lighthouse:` and the rake task is `lighthouse:ide_helper`, next to graphql-ruby's
|
|
49
|
-
`graphql:*` generators and `graphql:schema:*` tasks. `graphql:install` writes the
|
|
50
|
-
same controller and route; whichever runs second finds the route already in place,
|
|
51
|
-
and Lighthouse keeps a controller graphql-ruby generated. In that case, have the
|
|
52
|
-
controller call `AppSchema.execute(...)` to serve the Lighthouse schema.
|
|
43
|
+
`--schema=ApiSchema`, `--sdl-folder=app/schema`, `--subscriptions`,
|
|
44
|
+
`--skip-controller` and `--skip-ide` change what it does. Rerunning it changes
|
|
45
|
+
nothing, and a controller the app changed is kept.
|
|
46
|
+
[generators.md](generators.md) has the details, every generator Lighthouse has, and
|
|
47
|
+
which of graphql-ruby's `graphql:*` generators fit a Lighthouse app.
|
|
53
48
|
|
|
54
49
|
## 1. Write your schema (SDL)
|
|
55
50
|
|
|
@@ -79,22 +74,34 @@ field's return type name (`User`), or you can be explicit with
|
|
|
79
74
|
|
|
80
75
|
Lighthouse builds on graphql-ruby, and follows its convention: each kind of class
|
|
81
76
|
inherits from a base class in its own namespace, where what they all share goes
|
|
82
|
-
once. `
|
|
83
|
-
|
|
77
|
+
once. graphql-ruby's `graphql:install` writes them, and `lighthouse:install` builds
|
|
78
|
+
on them. By hand, run graphql-ruby's install with its defaults:
|
|
79
|
+
|
|
80
|
+
```shell
|
|
81
|
+
bin/rails generate graphql:install
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then include `Lighthouse::GraphQL::Type` in the base object, interface, union and
|
|
85
|
+
scalar, so every type class that inherits from them lends its methods to the SDL
|
|
86
|
+
type of its name (see [type classes](resolvers.md#type-classes)):
|
|
84
87
|
|
|
85
88
|
```ruby
|
|
86
|
-
# app/graphql/
|
|
87
|
-
module
|
|
88
|
-
class
|
|
89
|
+
# app/graphql/types/base_object.rb
|
|
90
|
+
module Types
|
|
91
|
+
class BaseObject < GraphQL::Schema::Object
|
|
92
|
+
include Lighthouse::GraphQL::Type
|
|
93
|
+
edge_type_class(Types::BaseEdge)
|
|
94
|
+
connection_type_class(Types::BaseConnection)
|
|
95
|
+
field_class Types::BaseField
|
|
96
|
+
# include ApolloFederation::Object # if you use federation
|
|
89
97
|
end
|
|
90
98
|
end
|
|
99
|
+
```
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class BaseMutation < GraphQL::Schema::Mutation
|
|
95
|
-
end
|
|
96
|
-
end
|
|
101
|
+
Add the subscription base, which graphql-ruby has none of, if the schema has a
|
|
102
|
+
`Subscription` type (`lighthouse:install --subscriptions` writes it):
|
|
97
103
|
|
|
104
|
+
```ruby
|
|
98
105
|
# app/graphql/subscriptions/base_subscription.rb — Subscription fields
|
|
99
106
|
module Subscriptions
|
|
100
107
|
class BaseSubscription < GraphQL::Schema::Subscription
|
|
@@ -103,61 +110,85 @@ module Subscriptions
|
|
|
103
110
|
end
|
|
104
111
|
```
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```ruby
|
|
111
|
-
# app/graphql/types/base_object.rb
|
|
112
|
-
module Types
|
|
113
|
-
class BaseObject < GraphQL::Schema::Object
|
|
114
|
-
# include ApolloFederation::Object # if you use federation
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
```
|
|
113
|
+
graphql-ruby's `Mutations::BaseMutation` stays a `RelayClassicMutation`. A
|
|
114
|
+
`Mutation` field that declares its arguments one by one calls it as a plain
|
|
115
|
+
mutation, with those arguments as keywords; one with a single `input:` argument
|
|
116
|
+
goes through `RelayClassicMutation`'s own `input:` handling.
|
|
118
117
|
|
|
119
118
|
## 3. Build the schema
|
|
120
119
|
|
|
120
|
+
graphql-ruby's schema class becomes Lighthouse's: it inherits from the schema
|
|
121
|
+
`get_schema` builds from the SDL, instead of `GraphQL::Schema`.
|
|
122
|
+
|
|
121
123
|
```ruby
|
|
122
124
|
# app/graphql/app_schema.rb
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
126
|
-
sdl_folder: Rails.root.join('graphql').to_s,
|
|
125
|
+
class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
127
126
|
options: {
|
|
128
127
|
base_types: {
|
|
128
|
+
query_type: Types::QueryType,
|
|
129
|
+
mutation_type: Types::MutationType,
|
|
130
|
+
node: Types::NodeType,
|
|
129
131
|
resolver: Resolvers::BaseResolver,
|
|
130
132
|
mutation: Mutations::BaseMutation,
|
|
131
133
|
subscription: Subscriptions::BaseSubscription,
|
|
132
134
|
object: Types::BaseObject,
|
|
133
|
-
|
|
135
|
+
interface: Types::BaseInterface,
|
|
136
|
+
union: Types::BaseUnion,
|
|
137
|
+
scalar: Types::BaseScalar,
|
|
138
|
+
enum: Types::BaseEnum,
|
|
139
|
+
input_object: Types::BaseInputObject
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
142
|
)
|
|
143
|
+
# Lighthouse batches relationship loads through graphql-ruby's Dataloader.
|
|
144
|
+
use GraphQL::Dataloader
|
|
145
|
+
|
|
146
|
+
max_depth(15)
|
|
147
|
+
max_query_string_tokens(5000)
|
|
148
|
+
validate_max_errors(100)
|
|
149
|
+
|
|
150
|
+
# Relay-style Object Identification, as graphql-ruby's install writes it:
|
|
151
|
+
def self.id_from_object(object, type_definition, query_ctx)
|
|
152
|
+
object.to_gid_param
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.object_from_id(global_id, query_ctx)
|
|
156
|
+
GlobalID.find(global_id)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
137
159
|
```
|
|
138
160
|
|
|
161
|
+
The SDL is read from `config.sdl_folder` (`get_schema(sdl_folder: ...)` overrides
|
|
162
|
+
it). The class body is graphql-ruby's, so plugins and settings go there as in any
|
|
163
|
+
graphql-ruby schema (`use ...`, `max_complexity(...)`), but for `query(...)` and
|
|
164
|
+
`mutation(...)`, which the SDL's root types replace, and `resolve_type`, which
|
|
165
|
+
Lighthouse answers (see [types.md](types.md)).
|
|
166
|
+
|
|
139
167
|
`object:` and the other SDL kinds go to graphql-ruby, which builds the SDL's types
|
|
140
|
-
from them. `
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
from them. `query_type:` and `mutation_type:` are the classes the SDL's `Query` and
|
|
169
|
+
`Mutation` are built on, so their own `field`s (Relay's `node` / `nodes`) are the
|
|
170
|
+
schema's too; `node:` is the SDL's `Node` interface (see [relay.md](relay.md)).
|
|
171
|
+
`resolver:`, `mutation:` and `subscription:` are Lighthouse's: every class a
|
|
172
|
+
`Query`, `Mutation` or `Subscription` field resolves to must inherit from its kind's
|
|
173
|
+
base, and a `Subscription` field with no class of its own resolves with the
|
|
174
|
+
subscription base. See [resolvers.md](resolvers.md#base-classes).
|
|
144
175
|
|
|
145
|
-
|
|
146
|
-
subclass further (e.g. to add federation, tracing, a Dataloader):
|
|
176
|
+
A constant works as well, with the settings passed as options or set on it:
|
|
147
177
|
|
|
148
178
|
```ruby
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
179
|
+
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
180
|
+
options: { base_types: { object: Types::BaseObject }, using: { GraphQL::Dataloader => {} } }
|
|
181
|
+
)
|
|
152
182
|
```
|
|
153
183
|
|
|
154
|
-
> Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads.
|
|
155
|
-
> `use GraphQL::Dataloader`
|
|
184
|
+
> Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads. Keep
|
|
185
|
+
> `use GraphQL::Dataloader` in your schema to get N+1-free relationships.
|
|
156
186
|
|
|
157
187
|
## 4. Serve it
|
|
158
188
|
|
|
159
|
-
`rails g lighthouse:install`
|
|
160
|
-
`Lighthouse::GraphQL::Controller`, a concern that
|
|
189
|
+
`rails g lighthouse:install` replaces graphql-ruby's controller (when the app has
|
|
190
|
+
not changed it) with one including `Lighthouse::GraphQL::Controller`, a concern that
|
|
191
|
+
serves the schema:
|
|
161
192
|
|
|
162
193
|
```ruby
|
|
163
194
|
class GraphqlController < ApplicationController
|
|
@@ -290,7 +321,9 @@ When a field needs Ruby, write a graphql-ruby resolver or mutation class named
|
|
|
290
321
|
after it — `Query.latestPosts` resolves to `Resolvers::LatestPosts`
|
|
291
322
|
(`app/graphql/resolvers/latest_posts.rb`, a `Resolvers::BaseResolver`),
|
|
292
323
|
`Mutation.publishPost` to `Mutations::PublishPost` (a `Mutations::BaseMutation`).
|
|
293
|
-
|
|
324
|
+
A method of the root type class comes first, as in graphql-ruby: `Query.latestPosts`
|
|
325
|
+
calls `Types::QueryType#latest_posts` when it defines one. `bin/rails generate
|
|
326
|
+
graphql:mutation PublishPost` writes a code-first mutation. See
|
|
294
327
|
[resolvers.md](resolvers.md).
|
|
295
328
|
|
|
296
329
|
## Next steps
|