lighthouse-graphql 0.5.0 → 0.7.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -1
  3. data/README.md +38 -22
  4. data/docs/best-practices.md +6 -4
  5. data/docs/caching.md +4 -3
  6. data/docs/configuration.md +20 -26
  7. data/docs/custom-directives.md +2 -1
  8. data/docs/directives.md +79 -14
  9. data/docs/federation.md +1 -1
  10. data/docs/filtering-and-ordering.md +330 -30
  11. data/docs/generators.md +42 -40
  12. data/docs/getting-started.md +128 -58
  13. data/docs/motivation.md +3 -2
  14. data/docs/performance.md +5 -4
  15. data/docs/relationships.md +7 -5
  16. data/docs/relay.md +63 -0
  17. data/docs/resolvers.md +92 -39
  18. data/docs/schema-organisation.md +177 -0
  19. data/docs/security.md +64 -16
  20. data/docs/subscriptions.md +16 -9
  21. data/docs/types.md +16 -17
  22. data/docs/uploads.md +41 -14
  23. data/lib/generators/lighthouse/base_classes.rb +9 -14
  24. data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
  25. data/lib/generators/lighthouse/install/install_generator.rb +125 -99
  26. data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +2 -1
  27. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +14 -13
  28. data/lib/lighthouse/ast_builder.rb +124 -0
  29. data/lib/lighthouse/caching.rb +9 -3
  30. data/lib/lighthouse/config.rb +27 -25
  31. data/lib/lighthouse/contracts/arg_manipulator.rb +9 -5
  32. data/lib/lighthouse/contracts/field_builder.rb +13 -0
  33. data/lib/lighthouse/controller.rb +28 -5
  34. data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +1 -2
  35. data/lib/lighthouse/directive_resolvers/all_resolver.rb +0 -2
  36. data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +18 -18
  37. data/lib/lighthouse/directive_resolvers/association_resolvers.rb +9 -6
  38. data/lib/lighthouse/directive_resolvers/base.rb +6 -37
  39. data/lib/lighthouse/directive_resolvers/builder_applier.rb +21 -6
  40. data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
  41. data/lib/lighthouse/directive_resolvers/count_resolver.rb +0 -1
  42. data/lib/lighthouse/directive_resolvers/first_resolver.rb +0 -2
  43. data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +3 -1
  44. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +2 -3
  45. data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +85 -26
  46. data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +12 -6
  47. data/lib/lighthouse/directives/arguments/more_filters.rb +1 -1
  48. data/lib/lighthouse/directives/arguments/order_by.rb +190 -26
  49. data/lib/lighthouse/directives/arguments/where_conditions.rb +25 -0
  50. data/lib/lighthouse/directives/arguments/where_conditions_base.rb +84 -0
  51. data/lib/lighthouse/directives/arguments/where_has_conditions.rb +56 -0
  52. data/lib/lighthouse/directives/definitions.rb +0 -1
  53. data/lib/lighthouse/directives/generated_types.rb +103 -0
  54. data/lib/lighthouse/directives/registry.rb +39 -10
  55. data/lib/lighthouse/document_ast.rb +111 -0
  56. data/lib/lighthouse/field_resolver_stack.rb +9 -6
  57. data/lib/lighthouse/global_id.rb +29 -3
  58. data/lib/lighthouse/graphql/version.rb +1 -1
  59. data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
  60. data/lib/lighthouse/ide_helper.rb +80 -23
  61. data/lib/lighthouse/namespaces.rb +1 -0
  62. data/lib/lighthouse/order_by/types.rb +137 -0
  63. data/lib/lighthouse/railtie.rb +9 -2
  64. data/lib/lighthouse/rb_lighthouse.rb +16 -5
  65. data/lib/lighthouse/resolve_info.rb +5 -5
  66. data/lib/lighthouse/resolver_bases.rb +18 -4
  67. data/lib/lighthouse/resolver_dispatch.rb +16 -1
  68. data/lib/lighthouse/root_types.rb +117 -0
  69. data/lib/lighthouse/schema_cache.rb +8 -4
  70. data/lib/lighthouse/schema_factory.rb +6 -9
  71. data/lib/lighthouse/schema_generator.rb +8 -18
  72. data/lib/lighthouse/schema_stitcher.rb +64 -0
  73. data/lib/lighthouse/support/naming.rb +0 -12
  74. data/lib/lighthouse/upload.rb +4 -2
  75. data/lib/lighthouse/upload_store.rb +39 -14
  76. data/lib/lighthouse/where_conditions/columns.rb +36 -0
  77. data/lib/lighthouse/where_conditions/handler.rb +152 -0
  78. data/lib/lighthouse/where_conditions/json_column.rb +99 -0
  79. data/lib/lighthouse/where_conditions/operator_map.rb +1 -4
  80. data/lib/lighthouse/where_conditions/types.rb +110 -0
  81. data/sig/lighthouse/directives.rbs +16 -3
  82. data/sig/lighthouse/graphql.rbs +48 -4
  83. data/sig/lighthouse.rbs +8 -12
  84. metadata +17 -7
  85. data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -29
  86. data/lib/generators/lighthouse/mutation_generator.rb +0 -31
  87. data/lib/generators/lighthouse/query_generator.rb +0 -30
  88. data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +0 -170
  89. data/lib/lighthouse/directives/where_conditions_directive.rb +0 -244
  90. data/lib/lighthouse/sdl_loader.rb +0 -22
@@ -24,29 +24,32 @@ 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
- | `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 |
40
+ | `graphql/schema.graphql` | The schema's entrypoint: a starter schema (`user` via `@find`, `users` via `@paginate`) that assumes a `User` model. Skipped when the file exists |
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`, `--schema-path=app/schema/schema.graphql`, `--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
 
46
49
  ## 1. Write your schema (SDL)
47
50
 
48
- By default Lighthouse loads every `*.graphql` file under `RAILS_ROOT/graphql`,
49
- sorted by path and concatenated. Split your schema however you like:
51
+ Lighthouse reads the schema from one entrypoint, `RAILS_ROOT/graphql/schema.graphql`
52
+ (`config.schema_path`), as PHP Lighthouse does:
50
53
 
51
54
  ```graphql
52
55
  # graphql/schema.graphql
@@ -67,29 +70,64 @@ argument (the `@eq` makes `id` a filter). Lighthouse infers the model from the
67
70
  field's return type name (`User`), or you can be explicit with
68
71
  `@all(model: "User")`.
69
72
 
73
+ To split the schema, the entrypoint imports the other files. A type is defined
74
+ once, and the other files add to it with `extend type`:
75
+
76
+ ```graphql
77
+ # graphql/schema.graphql
78
+ type Query
79
+
80
+ #import user.graphql
81
+ #import blog/*.graphql
82
+ ```
83
+
84
+ ```graphql
85
+ # graphql/user.graphql
86
+ type User {
87
+ id: ID!
88
+ name: String
89
+ }
90
+
91
+ extend type Query {
92
+ users: [User!]! @all
93
+ }
94
+ ```
95
+
96
+ A file nothing imports is not part of the schema, and a type defined twice is an
97
+ error. See [schema-organisation.md](schema-organisation.md).
98
+
70
99
  ## 2. Provide your base classes
71
100
 
72
101
  Lighthouse builds on graphql-ruby, and follows its convention: each kind of class
73
102
  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:`):
103
+ once. graphql-ruby's `graphql:install` writes them, and `lighthouse:install` builds
104
+ on them. By hand, run graphql-ruby's install with its defaults:
78
105
 
79
106
  ```shell
80
- bin/rails generate graphql:install --no-relay --skip-graphiql
107
+ bin/rails generate graphql:install
81
108
  ```
82
109
 
110
+ Then include `Lighthouse::GraphQL::Type` in the base object, interface, union and
111
+ scalar, so every type class that inherits from them lends its methods to the SDL
112
+ type of its name (see [type classes](resolvers.md#type-classes)):
113
+
83
114
  ```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
115
+ # app/graphql/types/base_object.rb
116
+ module Types
117
+ class BaseObject < GraphQL::Schema::Object
118
+ include Lighthouse::GraphQL::Type
119
+ edge_type_class(Types::BaseEdge)
120
+ connection_type_class(Types::BaseConnection)
88
121
  field_class Types::BaseField
89
- object_class Types::BaseObject
122
+ # include ApolloFederation::Object # if you use federation
90
123
  end
91
124
  end
125
+ ```
92
126
 
127
+ Add the subscription base, which graphql-ruby has none of, if the schema has a
128
+ `Subscription` type (`lighthouse:install --subscriptions` writes it):
129
+
130
+ ```ruby
93
131
  # app/graphql/subscriptions/base_subscription.rb — Subscription fields
94
132
  module Subscriptions
95
133
  class BaseSubscription < GraphQL::Schema::Subscription
@@ -98,62 +136,85 @@ module Subscriptions
98
136
  end
99
137
  ```
100
138
 
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
- ```
139
+ graphql-ruby's `Mutations::BaseMutation` stays a `RelayClassicMutation`. A
140
+ `Mutation` field that declares its arguments one by one calls it as a plain
141
+ mutation, with those arguments as keywords; one with a single `input:` argument
142
+ goes through `RelayClassicMutation`'s own `input:` handling.
114
143
 
115
144
  ## 3. Build the schema
116
145
 
146
+ graphql-ruby's schema class becomes Lighthouse's: it inherits from the schema
147
+ `get_schema` builds from the SDL, instead of `GraphQL::Schema`.
148
+
117
149
  ```ruby
118
150
  # 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,
151
+ class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
123
152
  options: {
124
153
  base_types: {
154
+ query_type: Types::QueryType,
155
+ mutation_type: Types::MutationType,
156
+ node: Types::NodeType,
125
157
  resolver: Resolvers::BaseResolver,
126
158
  mutation: Mutations::BaseMutation,
127
159
  subscription: Subscriptions::BaseSubscription,
128
160
  object: Types::BaseObject,
129
- # interface:, union:, enum:, input_object:, scalar: ...
161
+ interface: Types::BaseInterface,
162
+ union: Types::BaseUnion,
163
+ scalar: Types::BaseScalar,
164
+ enum: Types::BaseEnum,
165
+ input_object: Types::BaseInputObject
130
166
  }
131
167
  }
132
168
  )
169
+ # Lighthouse batches relationship loads through graphql-ruby's Dataloader.
170
+ use GraphQL::Dataloader
171
+
172
+ max_depth(15)
173
+ max_query_string_tokens(5000)
174
+ validate_max_errors(100)
175
+
176
+ # Relay-style Object Identification, as graphql-ruby's install writes it:
177
+ def self.id_from_object(object, type_definition, query_ctx)
178
+ object.to_gid_param
179
+ end
180
+
181
+ def self.object_from_id(global_id, query_ctx)
182
+ GlobalID.find(global_id)
183
+ end
184
+ end
133
185
  ```
134
186
 
187
+ The SDL is read from `config.schema_path` and the files it imports
188
+ (`get_schema(schema_path: ...)` overrides it). The class body is graphql-ruby's, so plugins and settings go there as in any
189
+ graphql-ruby schema (`use ...`, `max_complexity(...)`), but for `query(...)` and
190
+ `mutation(...)`, which the SDL's root types replace, and `resolve_type`, which
191
+ Lighthouse answers (see [types.md](types.md)).
192
+
135
193
  `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).
194
+ from them. `query_type:` and `mutation_type:` are the classes the SDL's `Query` and
195
+ `Mutation` are built on, so their own `field`s (Relay's `node` / `nodes`) are the
196
+ schema's too; `node:` is the SDL's `Node` interface (see [relay.md](relay.md)).
197
+ `resolver:`, `mutation:` and `subscription:` are Lighthouse's: every class a
198
+ `Query`, `Mutation` or `Subscription` field resolves to must inherit from its kind's
199
+ base, and a `Subscription` field with no class of its own resolves with the
200
+ subscription base. See [resolvers.md](resolvers.md#base-classes).
140
201
 
141
- `get_schema` returns a `GraphQL::Schema` subclass you can use directly, or
142
- subclass further (e.g. to add federation, tracing, a Dataloader):
202
+ A constant works as well, with the settings passed as options or set on it:
143
203
 
144
204
  ```ruby
145
- class AppSchema < AppSchema # or build a Base*Schema and subclass it
146
- use GraphQL::Dataloader
147
- end
205
+ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
206
+ options: { base_types: { object: Types::BaseObject }, using: { GraphQL::Dataloader => {} } }
207
+ )
148
208
  ```
149
209
 
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.
210
+ > Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads. Keep
211
+ > `use GraphQL::Dataloader` in your schema to get N+1-free relationships.
152
212
 
153
213
  ## 4. Serve it
154
214
 
155
- `rails g lighthouse:install` writes the controller. It includes
156
- `Lighthouse::GraphQL::Controller`, a concern that serves the schema:
215
+ `rails g lighthouse:install` replaces graphql-ruby's controller (when the app has
216
+ not changed it) with one including `Lighthouse::GraphQL::Controller`, a concern that
217
+ serves the schema:
157
218
 
158
219
  ```ruby
159
220
  class GraphqlController < ApplicationController
@@ -225,7 +286,9 @@ Your IDE validates and autocompletes `.graphql` files against the schema it can
225
286
  see. Lighthouse's directive definitions and the types its manipulation phase
226
287
  generates live in Ruby, so without help the IDE flags `@paginate` as unknown and
227
288
  can't resolve `UserPaginator`. PHP Lighthouse solves this with an IDE helper
228
- command; so does Lighthouse for Rails:
289
+ command, and so does Lighthouse for Rails. In development, an app with a
290
+ `graphql.config.yml` gets the files rewritten on boot and on every reload
291
+ (`config.ide_helper`), so you never run it by hand there. Elsewhere:
229
292
 
230
293
  ```shell
231
294
  bin/rails lighthouse:ide_helper # writes to Rails.root
@@ -239,8 +302,11 @@ It writes two files:
239
302
  declares (so the IDE never sees a duplicate).
240
303
  - **`programmatic-types.graphql`** — the types Lighthouse adds to the executable
241
304
  schema: `<Type>Paginator`, `<Type>SimplePaginator`, `<Type>Connection`/`Edge`,
242
- `PaginatorInfo`, `SimplePaginatorInfo`, `PageInfo`, the `whereConditions`
243
- inputs and enums, and the `_` placeholder scalar.
305
+ `PaginatorInfo`, `SimplePaginatorInfo`, `PageInfo`, the `@whereConditions` and
306
+ `@orderBy` inputs and enums, and the `_` placeholder scalar (without it, the
307
+ IDE reports `Unknown type "_"`). It leaves out every type the SDL or
308
+ `schema-directives.graphql` defines, because graphql-js, which the IDE plugins
309
+ use, rejects a type defined twice.
244
310
 
245
311
  Point your IDE's [graphql-config](https://the-guild.dev/graphql/config) at them
246
312
  next to your SDL — the install generator writes this `graphql.config.yml`:
@@ -254,7 +320,9 @@ schema:
254
320
 
255
321
  It works with the GraphQL plugin for JetBrains IDEs and the GraphQL extension for
256
322
  VS Code. The files are derived from your schema, so the generator adds them to
257
- `.gitignore`; rerun the task after changing the schema or registering a directive.
323
+ `.gitignore`. Outside development, rerun the task after changing the schema or
324
+ registering a directive. The IDE reads every `.graphql` file the glob matches, as
325
+ one schema: it does not follow `#import`.
258
326
 
259
327
  > The IDE reads fields as you wrote them: `users: [User!]! @paginate` still looks
260
328
  > like a list to it, although clients query `users { data { … } }`. Field
@@ -286,7 +354,9 @@ When a field needs Ruby, write a graphql-ruby resolver or mutation class named
286
354
  after it — `Query.latestPosts` resolves to `Resolvers::LatestPosts`
287
355
  (`app/graphql/resolvers/latest_posts.rb`, a `Resolvers::BaseResolver`),
288
356
  `Mutation.publishPost` to `Mutations::PublishPost` (a `Mutations::BaseMutation`).
289
- `bin/rails generate lighthouse:query LatestPosts` scaffolds one. See
357
+ A method of the root type class comes first, as in graphql-ruby: `Query.latestPosts`
358
+ calls `Types::QueryType#latest_posts` when it defines one. `bin/rails generate
359
+ graphql:mutation PublishPost` writes a code-first mutation. See
290
360
  [resolvers.md](resolvers.md).
291
361
 
292
362
  ## Next steps
data/docs/motivation.md CHANGED
@@ -98,8 +98,9 @@ generated from Ruby classes:
98
98
  - **It is designed before it is implemented.** You can agree on the types and fields
99
99
  first, as a team, and the server follows the design rather than leaking its internals.
100
100
  - **Tools understand it directly.** IDE plugins, code generators and schema registries
101
- read SDL. Lighthouse's `lighthouse:ide_helper` task adds its directives and generated
102
- types so editors validate and autocomplete the schema.
101
+ read SDL. Lighthouse writes out its directives and generated types (in development
102
+ on every reload, or with `lighthouse:ide_helper`), so editors validate and
103
+ autocomplete the schema.
103
104
  - **It says what each field does.** `posts: [Post!]! @paginate @guard @cacheControl(maxAge: 60)`
104
105
  tells a reader, without opening any Ruby, that the field is paginated, requires a
105
106
  signed-in user and can be cached for a minute.
data/docs/performance.md CHANGED
@@ -59,10 +59,11 @@ Lighthouse.configure { |config| config.watch_sdl = Rails.env.development? }
59
59
  ```
60
60
 
61
61
  Rails' reloader watches Ruby files, not `.graphql` ones, so without this an SDL
62
- edit only shows up after restarting the server. With it, the SDL folder joins
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.
62
+ edit only shows up after restarting the server. With it, the folder of
63
+ `config.schema_path` joins `config.watchable_dirs`, and an edit to any `.graphql`
64
+ file below it reloads the app, and with it the schema, on the next request, like
65
+ a Ruby change. The reload also rewrites the IDE helper files (`config.ide_helper`). This relies on the schema class living
66
+ in an autoloaded file, as `app/graphql/app_schema.rb` does.
66
67
 
67
68
  ## Persisted queries
68
69
 
@@ -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
@@ -173,7 +174,8 @@ queries:
173
174
  type User {
174
175
  publishedPosts(
175
176
  title: String @like
176
- orderBy: [OrderByClause!] @orderBy
177
+ orderBy: _ @orderBy(columns: ["title", "created_at"])
178
+ where: _ @whereConditions(columns: ["title"])
177
179
  ): [Post!]! @hasMany(relation: "posts", scopes: ["published"])
178
180
  }
179
181
  ```
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