lighthouse-graphql 0.5.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -1
  3. data/README.md +24 -16
  4. data/docs/caching.md +4 -3
  5. data/docs/configuration.md +1 -21
  6. data/docs/directives.md +14 -7
  7. data/docs/generators.md +41 -39
  8. data/docs/getting-started.md +88 -51
  9. data/docs/performance.md +2 -2
  10. data/docs/relationships.md +5 -4
  11. data/docs/relay.md +63 -0
  12. data/docs/resolvers.md +90 -37
  13. data/docs/security.md +64 -16
  14. data/docs/subscriptions.md +16 -9
  15. data/docs/types.md +16 -17
  16. data/docs/uploads.md +41 -14
  17. data/lib/generators/lighthouse/base_classes.rb +9 -14
  18. data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
  19. data/lib/generators/lighthouse/install/install_generator.rb +112 -91
  20. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +3 -9
  21. data/lib/lighthouse/caching.rb +9 -3
  22. data/lib/lighthouse/config.rb +6 -23
  23. data/lib/lighthouse/controller.rb +28 -5
  24. data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +18 -18
  25. data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
  26. data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +3 -1
  27. data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +85 -26
  28. data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +12 -6
  29. data/lib/lighthouse/directives/arguments/more_filters.rb +1 -1
  30. data/lib/lighthouse/field_resolver_stack.rb +9 -6
  31. data/lib/lighthouse/global_id.rb +29 -3
  32. data/lib/lighthouse/graphql/version.rb +1 -1
  33. data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
  34. data/lib/lighthouse/rb_lighthouse.rb +1 -0
  35. data/lib/lighthouse/resolver_bases.rb +18 -4
  36. data/lib/lighthouse/resolver_dispatch.rb +16 -1
  37. data/lib/lighthouse/root_types.rb +117 -0
  38. data/lib/lighthouse/schema_generator.rb +4 -3
  39. data/lib/lighthouse/upload.rb +4 -2
  40. data/lib/lighthouse/upload_store.rb +39 -14
  41. data/sig/lighthouse.rbs +0 -10
  42. metadata +3 -4
  43. data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -29
  44. data/lib/generators/lighthouse/mutation_generator.rb +0 -31
  45. data/lib/generators/lighthouse/query_generator.rb +0 -30
@@ -24,22 +24,25 @@ 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. It runs graphql-ruby's `graphql:install` first, for
28
- the base classes every graphql-ruby app has (`Types::Base*`, `Resolvers::BaseResolver`,
29
- `Mutations::BaseMutation`), then puts Lighthouse's own on top:
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:
30
32
 
31
33
  | File | What it is |
32
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` |
33
39
  | `config/initializers/lighthouse.rb` | Every [configuration](configuration.md) option, commented out at its default |
34
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 |
35
- | `app/graphql/app_schema.rb` | The executable schema, built from the SDL, with `GraphQL::Dataloader` enabled and the base classes in `base_types` |
36
- | `app/graphql/{types,resolvers,mutations,subscriptions}/base_*.rb` | graphql-ruby's base classes, and Lighthouse's `Subscriptions::BaseSubscription` |
37
- | `app/controllers/graphql_controller.rb` | The endpoint, plus a `post '/graphql'` route |
38
41
  | `graphql.config.yml` | IDE configuration — see [IDE support](#ide-support) |
39
42
 
40
- `--schema=ApiSchema`, `--sdl-folder=app/schema`, `--skip-controller` and
41
- `--skip-ide` change what it writes. Rerunning it is safe, and a schema class or
42
- controller the app had before is kept (pass `--force` to replace it).
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.
43
46
  [generators.md](generators.md) has the details, every generator Lighthouse has, and
44
47
  which of graphql-ruby's `graphql:*` generators fit a Lighthouse app.
45
48
 
@@ -71,25 +74,34 @@ field's return type name (`User`), or you can be explicit with
71
74
 
72
75
  Lighthouse builds on graphql-ruby, and follows its convention: each kind of class
73
76
  inherits from a base class in its own namespace, where what they all share goes
74
- once. graphql-ruby's `graphql:install` writes them, and `lighthouse:install` runs it;
75
- by hand, run it yourself, then add the subscription base, which graphql-ruby has
76
- none of, and make the mutation base a `GraphQL::Schema::Mutation` (the SDL declares a
77
- mutation's arguments, where graphql-ruby's `RelayClassicMutation` takes one `input:`):
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:
78
79
 
79
80
  ```shell
80
- bin/rails generate graphql:install --no-relay --skip-graphiql
81
+ bin/rails generate graphql:install
81
82
  ```
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)):
87
+
83
88
  ```ruby
84
- # app/graphql/mutations/base_mutation.rb — Mutation fields (Mutations::PublishPost)
85
- module Mutations
86
- class BaseMutation < GraphQL::Schema::Mutation # graphql-ruby writes RelayClassicMutation
87
- argument_class Types::BaseArgument
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)
88
95
  field_class Types::BaseField
89
- object_class Types::BaseObject
96
+ # include ApolloFederation::Object # if you use federation
90
97
  end
91
98
  end
99
+ ```
100
+
101
+ Add the subscription base, which graphql-ruby has none of, if the schema has a
102
+ `Subscription` type (`lighthouse:install --subscriptions` writes it):
92
103
 
104
+ ```ruby
93
105
  # app/graphql/subscriptions/base_subscription.rb — Subscription fields
94
106
  module Subscriptions
95
107
  class BaseSubscription < GraphQL::Schema::Subscription
@@ -98,62 +110,85 @@ module Subscriptions
98
110
  end
99
111
  ```
100
112
 
101
- Delete the `Types::QueryType` and `Types::MutationType` it writes: the SDL declares
102
- the root types. The `Types::Base*` classes are the SDL's types' bases: the place to
103
- mix in Apollo Federation or custom field and argument behavior.
104
-
105
- ```ruby
106
- # app/graphql/types/base_object.rb
107
- module Types
108
- class BaseObject < GraphQL::Schema::Object
109
- field_class Types::BaseField
110
- # include ApolloFederation::Object # if you use federation
111
- end
112
- end
113
- ```
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.
114
117
 
115
118
  ## 3. Build the schema
116
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
+
117
123
  ```ruby
118
124
  # app/graphql/app_schema.rb
119
- require 'lighthouse-graphql'
120
-
121
- AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
122
- sdl_folder: Rails.root.join('graphql').to_s,
125
+ class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
123
126
  options: {
124
127
  base_types: {
128
+ query_type: Types::QueryType,
129
+ mutation_type: Types::MutationType,
130
+ node: Types::NodeType,
125
131
  resolver: Resolvers::BaseResolver,
126
132
  mutation: Mutations::BaseMutation,
127
133
  subscription: Subscriptions::BaseSubscription,
128
134
  object: Types::BaseObject,
129
- # interface:, union:, enum:, input_object:, scalar: ...
135
+ interface: Types::BaseInterface,
136
+ union: Types::BaseUnion,
137
+ scalar: Types::BaseScalar,
138
+ enum: Types::BaseEnum,
139
+ input_object: Types::BaseInputObject
130
140
  }
131
141
  }
132
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
133
159
  ```
134
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
+
135
167
  `object:` and the other SDL kinds go to graphql-ruby, which builds the SDL's types
136
- from them. `resolver:`, `mutation:` and `subscription:` are Lighthouse's: every
137
- class a `Query`, `Mutation` or `Subscription` field resolves to must inherit from
138
- its kind's base, and a `Subscription` field with no class of its own resolves with
139
- the subscription base. See [resolvers.md](resolvers.md#base-classes).
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).
140
175
 
141
- `get_schema` returns a `GraphQL::Schema` subclass you can use directly, or
142
- 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:
143
177
 
144
178
  ```ruby
145
- class AppSchema < AppSchema # or build a Base*Schema and subclass it
146
- use GraphQL::Dataloader
147
- end
179
+ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
180
+ options: { base_types: { object: Types::BaseObject }, using: { GraphQL::Dataloader => {} } }
181
+ )
148
182
  ```
149
183
 
150
- > Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads. Add
151
- > `use GraphQL::Dataloader` to your schema to get N+1-free relationships.
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.
152
186
 
153
187
  ## 4. Serve it
154
188
 
155
- `rails g lighthouse:install` writes the controller. It includes
156
- `Lighthouse::GraphQL::Controller`, a concern that serves the schema:
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:
157
192
 
158
193
  ```ruby
159
194
  class GraphqlController < ApplicationController
@@ -286,7 +321,9 @@ When a field needs Ruby, write a graphql-ruby resolver or mutation class named
286
321
  after it — `Query.latestPosts` resolves to `Resolvers::LatestPosts`
287
322
  (`app/graphql/resolvers/latest_posts.rb`, a `Resolvers::BaseResolver`),
288
323
  `Mutation.publishPost` to `Mutations::PublishPost` (a `Mutations::BaseMutation`).
289
- `bin/rails generate lighthouse:query LatestPosts` scaffolds one. See
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
290
327
  [resolvers.md](resolvers.md).
291
328
 
292
329
  ## Next steps
data/docs/performance.md CHANGED
@@ -61,8 +61,8 @@ Lighthouse.configure { |config| config.watch_sdl = Rails.env.development? }
61
61
  Rails' reloader watches Ruby files, not `.graphql` ones, so without this an SDL
62
62
  edit only shows up after restarting the server. With it, the SDL folder joins
63
63
  `config.watchable_dirs`, and an edit reloads the app, and with it the schema, on
64
- the next request, like a Ruby change. This relies on the schema constant living
65
- in an autoloaded file, as the generated `app/graphql/app_schema.rb` does.
64
+ the next request, like a Ruby change. This relies on the schema class living
65
+ in an autoloaded file, as `app/graphql/app_schema.rb` does.
66
66
 
67
67
  ## Persisted queries
68
68
 
@@ -39,12 +39,13 @@ aliases, so an SDL written for it builds unchanged.
39
39
 
40
40
  The relationship directives load through graphql-ruby's **Dataloader** and
41
41
  ActiveRecord's preloader, one query per association per batch. Enable the
42
- Dataloader on your schema (the install generator does):
42
+ Dataloader on your schema (graphql-ruby's install does, and with `--batch`
43
+ `lighthouse:install` adds it beside GraphQL::Batch):
43
44
 
44
45
  ```ruby
45
- AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
46
- options: { using: { GraphQL::Dataloader => {} } }
47
- )
46
+ class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(options: { base_types: { object: Types::BaseObject } })
47
+ use GraphQL::Dataloader
48
+ end
48
49
  ```
49
50
 
50
51
  A query for 100 posts and their authors then issues two queries, not 101. A
data/docs/relay.md CHANGED
@@ -6,6 +6,11 @@ does, with two directives: `@globalId` encodes and decodes the ids, and `@node`
6
6
  makes a type findable by them. For Relay's cursor pagination, see
7
7
  [`@paginate(type: CONNECTION)`](directives.md#paginate).
8
8
 
9
+ An app whose schema has graphql-ruby's Relay, as `rails g graphql:install` writes it
10
+ by default, uses the same directives on graphql-ruby's ids and `Node`: see
11
+ [Relay through graphql-ruby](#relay-through-graphql-ruby). The sections before it
12
+ describe a schema without it.
13
+
9
14
  ## Global ids
10
15
 
11
16
  `@globalId` on a field encodes its value with the type's name:
@@ -125,6 +130,64 @@ type Query {
125
130
  }
126
131
  ```
127
132
 
133
+ ## Relay through graphql-ruby
134
+
135
+ graphql-ruby's install (`--relay`, its default) writes Relay's side in Ruby:
136
+ `Types::NodeType`, the `node` and `nodes` fields of `Types::QueryType`, and the
137
+ schema's `id_from_object` / `object_from_id`, on Rails' GlobalID:
138
+
139
+ ```ruby
140
+ class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
141
+ options: { base_types: { query_type: Types::QueryType, node: Types::NodeType, object: Types::BaseObject } }
142
+ )
143
+ def self.id_from_object(object, type_definition, query_ctx)
144
+ object.to_gid_param
145
+ end
146
+
147
+ def self.object_from_id(global_id, query_ctx)
148
+ GlobalID.find(global_id)
149
+ end
150
+ end
151
+ ```
152
+
153
+ `lighthouse:install` names those classes in `base_types`, and Lighthouse's
154
+ directives then work on them. The schema has one kind of global id and one `Node`:
155
+
156
+ - `@globalId` on a field that resolves an object's own id encodes it with
157
+ `id_from_object`: `Tag.id` answers `tag.to_gid_param`, as graphql-ruby's `Node.id`
158
+ does. Any other value is encoded as before.
159
+ - `@globalId` on an argument decodes with `object_from_id`, which loads the object.
160
+ `ID` gives its id, and `ARRAY` / `TYPE` its class name (`["Tag", 1]`, `"Tag"`).
161
+ - `node:` is the SDL's `Node` interface: `interface Node`, which `@node` adds, and
162
+ `implements Node` refer to `Types::NodeType`. A `@node` type implements it.
163
+ - `Query.node` and `nodes` are graphql-ruby's code-first fields of
164
+ `Types::QueryType`, which find objects with `object_from_id`. The `node` field
165
+ `@node` would add to `Query` gives way to them.
166
+
167
+ ```graphql
168
+ type Tag @node {
169
+ id: ID! @globalId
170
+ name: String!
171
+ }
172
+
173
+ type Query {
174
+ tag(id: ID! @globalId(decode: ID) @eq): Tag @find
175
+ }
176
+ ```
177
+
178
+ ```graphql
179
+ {
180
+ node(id: "Z2lkOi8vYXBwL1RhZy8x") { id ... on Tag { name } }
181
+ tag(id: "Z2lkOi8vYXBwL1RhZy8x") { name }
182
+ }
183
+ ```
184
+
185
+ `Tag.id` and both arguments carry `Tag` 1's GlobalID (`gid://app/Tag/1`, in base64).
186
+ `Query.node` finds every object with `object_from_id`, so `@node(resolver:)` and
187
+ `@node(model:)` play no part in it, and [`global_id`](configuration.md#global_id)
188
+ encodes only what `id_from_object` does not. A schema without `id_from_object` /
189
+ `object_from_id` works as the sections above describe.
190
+
128
191
  ## Your own encoding
129
192
 
130
193
  [`global_id`](configuration.md#global_id) takes any object with
data/docs/resolvers.md CHANGED
@@ -8,9 +8,9 @@ interchangeable. Each kind has its own contract and its own folder:
8
8
  | Kind | Folder / namespace | Contract | Used by |
9
9
  | --- | --- | --- | --- |
10
10
  | Resolver | `app/graphql/resolvers` · `Resolvers::` | `Resolvers::BaseResolver` (a `GraphQL::Schema::Resolver`) | `Query` fields |
11
- | Mutation | `app/graphql/mutations` · `Mutations::` | `Mutations::BaseMutation` (a `GraphQL::Schema::Mutation`) | `Mutation` fields |
11
+ | Mutation | `app/graphql/mutations` · `Mutations::` | `Mutations::BaseMutation` (graphql-ruby's `GraphQL::Schema::RelayClassicMutation`) | `Mutation` fields |
12
12
  | Subscription | `app/graphql/subscriptions` · `Subscriptions::` | `Subscriptions::BaseSubscription` (a `GraphQL::Schema::Subscription`) | `Subscription` fields ([subscriptions.md](subscriptions.md)) |
13
- | Type | `app/graphql/types` · `Types::` | `Types::BaseObject` (a `GraphQL::Schema::Object`) including `Lighthouse::GraphQL::Type`, named `<Type>Type` | Fields of other types, through its methods ([type classes](#type-classes)); unions, interfaces and scalars too ([types.md](types.md)) |
13
+ | Type | `app/graphql/types` · `Types::` | `Types::BaseObject` (a `GraphQL::Schema::Object`) including `Lighthouse::GraphQL::Type`, named `<Type>Type` | Fields of any object type, through its methods ([type classes](#type-classes), [root type classes](#root-type-classes)); unions, interfaces and scalars too ([types.md](types.md)) |
14
14
  | Builder | `app/graphql/builders` · `Builders::` | `call(relation, args, context)` → relation | `@builder` |
15
15
 
16
16
  Validators (`app/graphql/validators`) are one more kind. They are covered in
@@ -19,12 +19,13 @@ Validators (`app/graphql/validators`) are one more kind. They are covered in
19
19
  ## Base classes
20
20
 
21
21
  As in any graphql-ruby app, each kind of class has a base class in its namespace,
22
- and every class of the kind inherits from it. graphql-ruby's `graphql:install`,
23
- which `rails g lighthouse:install` runs, writes `Resolvers::BaseResolver`,
24
- `Mutations::BaseMutation` and the `Types::Base*` classes; Lighthouse adds
25
- `Subscriptions::BaseSubscription`, which graphql-ruby has none of, and makes the
26
- mutation base a plain `GraphQL::Schema::Mutation`, as the SDL declares a mutation's
27
- arguments ([generators.md](generators.md)):
22
+ and every class of the kind inherits from it. graphql-ruby's `graphql:install`
23
+ writes `Resolvers::BaseResolver`, `Mutations::BaseMutation` and the `Types::Base*`
24
+ classes, and `rails g lighthouse:install` builds on them: it keeps them as
25
+ graphql-ruby wrote them, includes `Lighthouse::GraphQL::Type` in the base object,
26
+ interface, union and scalar, and with `--subscriptions` adds
27
+ `Subscriptions::BaseSubscription`, which graphql-ruby has none of
28
+ ([generators.md](generators.md)):
28
29
 
29
30
  ```ruby
30
31
  # app/graphql/resolvers/base_resolver.rb (graphql-ruby's)
@@ -33,16 +34,17 @@ module Resolvers
33
34
  end
34
35
  end
35
36
 
36
- # app/graphql/mutations/base_mutation.rb (graphql-ruby's, made a Mutation)
37
+ # app/graphql/mutations/base_mutation.rb (graphql-ruby's)
37
38
  module Mutations
38
- class BaseMutation < GraphQL::Schema::Mutation
39
+ class BaseMutation < GraphQL::Schema::RelayClassicMutation
39
40
  argument_class Types::BaseArgument
40
41
  field_class Types::BaseField
42
+ input_object_class Types::BaseInputObject
41
43
  object_class Types::BaseObject
42
44
  end
43
45
  end
44
46
 
45
- # app/graphql/subscriptions/base_subscription.rb (Lighthouse's)
47
+ # app/graphql/subscriptions/base_subscription.rb (Lighthouse's, --subscriptions)
46
48
  module Subscriptions
47
49
  class BaseSubscription < GraphQL::Schema::Subscription
48
50
  include Lighthouse::GraphQL::Subscription
@@ -62,9 +64,12 @@ schema holds every class to them:
62
64
 
63
65
  ```ruby
64
66
  # app/graphql/app_schema.rb
65
- AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
67
+ class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
66
68
  options: {
67
69
  base_types: {
70
+ query_type: Types::QueryType,
71
+ mutation_type: Types::MutationType,
72
+ node: Types::NodeType,
68
73
  resolver: Resolvers::BaseResolver,
69
74
  mutation: Mutations::BaseMutation,
70
75
  subscription: Subscriptions::BaseSubscription,
@@ -77,6 +82,8 @@ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
77
82
  }
78
83
  }
79
84
  )
85
+ use GraphQL::Dataloader
86
+ end
80
87
  ```
81
88
 
82
89
  - A class a `Query`, `Mutation` or `Subscription` field resolves to, by convention
@@ -89,12 +96,17 @@ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
89
96
  (`GraphQL::Schema::Resolver`, `GraphQL::Schema::Mutation`, which
90
97
  `RelayClassicMutation` is, `GraphQL::Schema::Subscription`), or the schema does not
91
98
  build. graphql-ruby never sees these three keys.
99
+ - `query_type:` and `mutation_type:` are the code-first classes the SDL's `Query`
100
+ and `Mutation` are built on (see [root type classes](#root-type-classes)), and
101
+ `node:` is the SDL's `Node` interface ([relay.md](relay.md#relay-through-graphql-ruby)).
102
+ `subscription_type:` does the same for `Subscription`.
92
103
  - The others are graphql-ruby's: it builds the SDL's types of each kind from them.
93
104
  A [type class](#type-classes) (one including `Lighthouse::GraphQL::Type`) that
94
105
  does not inherit from its kind's raises `Lighthouse::DefinitionError` when the
95
106
  schema is built.
96
107
 
97
- `rails g lighthouse:install` writes the schema class with them. Without them,
108
+ `rails g lighthouse:install` names in the schema class every one graphql-ruby's
109
+ install wrote. Without them,
98
110
  Lighthouse requires only the graphql-ruby class (`GraphQL::Schema::Resolver`, …),
99
111
  and a `Subscription` field with no class resolves with a built-in subscription that
100
112
  includes the concern.
@@ -149,8 +161,9 @@ end
149
161
 
150
162
  ### Resolution by convention
151
163
 
152
- A field on `Query` or `Mutation` with **no resolving directive** resolves to the
153
- class named after it, as in PHP Lighthouse:
164
+ A field on `Query` or `Mutation` with **no resolving directive**, and no method of
165
+ its name on the [root type class](#root-type-classes), resolves to the class named
166
+ after it, as in PHP Lighthouse:
154
167
 
155
168
  - `Query.latestPosts` resolves to `Resolvers::LatestPosts`;
156
169
  - `Mutation.publishPost` resolves to `Mutations::PublishPost`.
@@ -162,9 +175,11 @@ field with no class is reported through [`on_error`](configuration.md#on_error):
162
175
  it raises in test and logs elsewhere. Lighthouse never falls back to a top-level
163
176
  constant, so a `post` field is never mistaken for the `Post` model.
164
177
 
165
- graphql-ruby's `RelayClassicMutation` (what `rails g graphql:install` makes
166
- `Mutations::BaseMutation` inherit from) works too, as long as the SDL field takes the
167
- `input:` argument it expects.
178
+ graphql-ruby's `RelayClassicMutation`, which `Mutations::BaseMutation` inherits from,
179
+ takes a single `input:` argument. A `Mutation` field whose SDL declares its arguments
180
+ one by one calls the class as a plain mutation, with those arguments as keywords
181
+ (`resolve(id:)` above). A field with a single `input:` argument goes through
182
+ `RelayClassicMutation`'s own `input:` handling.
168
183
 
169
184
  Wrapping directives (`@guard`, the `@can*` family, `@validator`, `@cache`,
170
185
  `@clearCache`) are not resolvers, so a field carrying only those still resolves by
@@ -176,6 +191,45 @@ type Mutation {
176
191
  }
177
192
  ```
178
193
 
194
+ ### Root type classes
195
+
196
+ graphql-ruby's install writes `Types::QueryType` and `Types::MutationType`, and the
197
+ schema's `base_types` name them (`query_type:`, `mutation_type:`). The SDL's `Query`
198
+ and `Mutation` are built on them, so they work as in a graphql-ruby app:
199
+
200
+ - A root field with no resolving directive calls the method of its snake_case name
201
+ on the class, when the class defines it and includes `Lighthouse::GraphQL::Type`
202
+ (`Types::BaseObject` does, after `lighthouse:install`). It comes before the
203
+ conventional class: `Query.latestPosts` calls `Types::QueryType#latest_posts`
204
+ before `Resolvers::LatestPosts`.
205
+ - The class's own `field`s are the schema's too, beside the SDL's, and resolve as
206
+ in graphql-ruby: Relay's `node` / `nodes`, and the `field :publish_post, mutation:
207
+ Mutations::PublishPost` that `rails g graphql:mutation` adds. An SDL field of the
208
+ same name wins.
209
+ - When the SDL has no `Mutation` type, `Types::MutationType` itself is the root,
210
+ once it declares a field.
211
+
212
+ ```graphql
213
+ type Query {
214
+ latestPosts(limit: Int = 10): [Post!]!
215
+ }
216
+ ```
217
+
218
+ ```ruby
219
+ # app/graphql/types/query_type.rb
220
+ module Types
221
+ class QueryType < Types::BaseObject
222
+ def latest_posts(limit:)
223
+ Post.order(created_at: :desc).limit(limit)
224
+ end
225
+ end
226
+ end
227
+ ```
228
+
229
+ As in a [type class](#type-classes), the method has `object`, `context`, the
230
+ `dataload*` helpers and `resolve_info`, and gets the field's arguments as snake_case
231
+ keywords. `Subscription` fields resolve with their classes, as before.
232
+
179
233
  ### Naming a class explicitly
180
234
 
181
235
  `@field(resolver:)` points a field at a class by name. On `Query` and `Mutation`
@@ -317,8 +371,9 @@ what every resolver shares.
317
371
 
318
372
  graphql-ruby keeps the Ruby side of each object type in `app/graphql/types`:
319
373
  `Types::PostType`, inheriting from `Types::BaseObject`, as `rails g graphql:object
320
- Post` writes it. Lighthouse keeps that structure. A type class that includes `Lighthouse::GraphQL::Type` is handed to
321
- Lighthouse: the SDL still declares the type and its fields, and the class says how
374
+ Post` writes it. Lighthouse keeps that structure. A type class that includes
375
+ `Lighthouse::GraphQL::Type` is handed to Lighthouse. After `lighthouse:install`,
376
+ `Types::BaseObject` includes it, so every type class does: the SDL still declares the type and its fields, and the class says how
322
377
  the fields with no resolving directive resolve. A field calls the method of its
323
378
  snake_case name, when the class defines it. Fields it does not define are read off
324
379
  the object, as before, and fields with a resolving directive keep it.
@@ -343,8 +398,6 @@ type ReadingTime {
343
398
  # app/graphql/types/post_type.rb
344
399
  module Types
345
400
  class PostType < BaseObject
346
- include Lighthouse::GraphQL::Type
347
-
348
401
  def reading_time
349
402
  { words: object.body.to_s.split.size }
350
403
  end
@@ -354,8 +407,6 @@ end
354
407
  # app/graphql/types/reading_time_type.rb
355
408
  module Types
356
409
  class ReadingTimeType < BaseObject
357
- include Lighthouse::GraphQL::Type
358
-
359
410
  def minutes(words_per_minute:)
360
411
  [(object[:words] / words_per_minute.to_f).ceil, 1].max
361
412
  end
@@ -403,9 +454,11 @@ interface to `Article` ([types.md](types.md#the-default)).
403
454
 
404
455
  The class is the type's name plus `Type`, looked up in `namespaces[:types]`
405
456
  (default `['Types']`) when the schema is built. Only a class that includes
406
- `Lighthouse::GraphQL::Type` counts: any other `Types::PostType`, such as the ones
407
- a graphql-ruby app already has, is left alone, and its methods resolve nothing.
408
- Include the concern in `Types::BaseObject` to hand every type class over at once.
457
+ `Lighthouse::GraphQL::Type` counts: any other `Types::PostType` is left alone, and
458
+ its methods resolve nothing. `lighthouse:install` includes the concern in
459
+ `Types::BaseObject`, `BaseInterface`, `BaseUnion` and `BaseScalar`, which hands every
460
+ type class over at once. In an app that did not run it, include the concern in
461
+ those bases, or in each type class (`include Lighthouse::GraphQL::Type`).
409
462
 
410
463
  A class that includes the concern must inherit from the schema's
411
464
  `base_types[:object]` when one is given, or the schema does not build. The concern
@@ -413,9 +466,9 @@ also hands over the classes of unions, interfaces and scalars, which pick the ty
413
466
  of a union's values or coerce a scalar's: see [types.md](types.md). It is not the schema's type: it only lends its
414
467
  methods. Fields are declared in the SDL, so `field` declarations in it are ignored,
415
468
  and graphql-ruby's type-level `authorized?` does not run; authorize with
416
- [`@can*`](authorization.md). `Query`, `Mutation` and `Subscription` fields resolve
417
- with their own classes, [by convention](#resolution-by-convention), not with a type
418
- class.
469
+ [`@can*`](authorization.md). `Query` and `Mutation` are the exception: they are
470
+ built on their [root type classes](#root-type-classes), whose `field`s count.
471
+ `Subscription` fields resolve with their own classes.
419
472
 
420
473
  ### `@spread` and `@inject`
421
474
 
@@ -476,18 +529,18 @@ end
476
529
  ## Generators
477
530
 
478
531
  ```shell
479
- bin/rails generate lighthouse:query LatestPosts # app/graphql/resolvers/latest_posts.rb
480
- bin/rails generate lighthouse:mutation PublishPost # app/graphql/mutations/publish_post.rb
481
532
  bin/rails generate lighthouse:subscription PostUpdated # app/graphql/subscriptions/post_updated.rb
482
533
  bin/rails generate lighthouse:builder Published # app/graphql/builders/published.rb
483
534
  bin/rails generate lighthouse:validator CreatePostInput # app/graphql/validators/create_post_input_validator.rb
484
- bin/rails generate graphql:object Post # a type class: then include Lighthouse::GraphQL::Type
535
+ bin/rails generate graphql:object Post # a type class, Lighthouse's through Types::BaseObject
536
+ bin/rails generate graphql:mutation PublishPost # a code-first mutation, a field of Types::MutationType
485
537
  ```
486
538
 
487
- Each writes the class with its contract, into the first namespace configured for its
488
- kind (or `--namespace=…`), inheriting from the namespace's base class. Type classes
489
- come from graphql-ruby's own `graphql:object`, `graphql:scalar`, `graphql:union` and
490
- `graphql:interface`. [generators.md](generators.md) has what each generator writes,
539
+ Each Lighthouse generator writes the class with its contract, into the first
540
+ namespace configured for its kind (or `--namespace=…`). Type classes come from
541
+ graphql-ruby's own `graphql:object`, `graphql:scalar`, `graphql:union` and
542
+ `graphql:interface`. A resolver class has no generator, as in graphql-ruby: write
543
+ it by hand, as above. [generators.md](generators.md) has what each generator writes,
491
544
  and which of graphql-ruby's fit a Lighthouse app.
492
545
 
493
546
  ## Changing the namespaces