lighthouse-graphql 0.1.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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +134 -0
  5. data/docs/authorization.md +109 -0
  6. data/docs/best-practices.md +75 -0
  7. data/docs/configuration.md +70 -0
  8. data/docs/custom-directives.md +129 -0
  9. data/docs/directives.md +298 -0
  10. data/docs/federation.md +108 -0
  11. data/docs/filtering-and-ordering.md +128 -0
  12. data/docs/getting-started.md +152 -0
  13. data/docs/relationships.md +95 -0
  14. data/lib/lighthouse/config.rb +112 -0
  15. data/lib/lighthouse/contracts/arg_builder.rb +17 -0
  16. data/lib/lighthouse/contracts/arg_manipulator.rb +17 -0
  17. data/lib/lighthouse/contracts/directive.rb +17 -0
  18. data/lib/lighthouse/contracts/field_manipulator.rb +17 -0
  19. data/lib/lighthouse/contracts/field_middleware.rb +16 -0
  20. data/lib/lighthouse/contracts/field_resolver.rb +18 -0
  21. data/lib/lighthouse/dataloader_sources/association_loader.rb +25 -0
  22. data/lib/lighthouse/dataloader_sources/model_loader.rb +19 -0
  23. data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +86 -0
  24. data/lib/lighthouse/directive_resolvers/all_resolver.rb +73 -0
  25. data/lib/lighthouse/directive_resolvers/auth_resolver.rb +44 -0
  26. data/lib/lighthouse/directive_resolvers/base.rb +92 -0
  27. data/lib/lighthouse/directive_resolvers/belongs_to_many_resolver.rb +56 -0
  28. data/lib/lighthouse/directive_resolvers/belongs_to_resolver.rb +47 -0
  29. data/lib/lighthouse/directive_resolvers/builder_applier.rb +63 -0
  30. data/lib/lighthouse/directive_resolvers/can_resolver.rb +86 -0
  31. data/lib/lighthouse/directive_resolvers/count_resolver.rb +56 -0
  32. data/lib/lighthouse/directive_resolvers/field_resolver.rb +78 -0
  33. data/lib/lighthouse/directive_resolvers/find_resolver.rb +65 -0
  34. data/lib/lighthouse/directive_resolvers/first_resolver.rb +53 -0
  35. data/lib/lighthouse/directive_resolvers/guard_resolver.rb +32 -0
  36. data/lib/lighthouse/directive_resolvers/has_many_resolver.rb +109 -0
  37. data/lib/lighthouse/directive_resolvers/has_one_resolver.rb +43 -0
  38. data/lib/lighthouse/directive_resolvers/method_resolver.rb +35 -0
  39. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +147 -0
  40. data/lib/lighthouse/directive_resolvers/rename_resolver.rb +35 -0
  41. data/lib/lighthouse/directive_resolvers/resolution.rb +35 -0
  42. data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +169 -0
  43. data/lib/lighthouse/directives/arguments/base.rb +40 -0
  44. data/lib/lighthouse/directives/arguments/filters.rb +101 -0
  45. data/lib/lighthouse/directives/arguments/order_by.rb +48 -0
  46. data/lib/lighthouse/directives/registry.rb +168 -0
  47. data/lib/lighthouse/directives/relation_directive.rb +332 -0
  48. data/lib/lighthouse/directives/where_conditions_directive.rb +208 -0
  49. data/lib/lighthouse/graphql/version.rb +7 -0
  50. data/lib/lighthouse/graphql.rb +8 -0
  51. data/lib/lighthouse/rb_lighthouse.rb +88 -0
  52. data/lib/lighthouse/reference_resolver.rb +24 -0
  53. data/lib/lighthouse/schema_factory.rb +46 -0
  54. data/lib/lighthouse/schema_generator.rb +244 -0
  55. data/lib/lighthouse/schema_implementation.rb +119 -0
  56. data/lib/lighthouse/sdl_loader.rb +27 -0
  57. data/lib/lighthouse/support/authorization.rb +26 -0
  58. data/lib/lighthouse/support/directive_args.rb +33 -0
  59. data/lib/lighthouse/support/model_resolver.rb +61 -0
  60. data/lib/lighthouse/support/naming.rb +71 -0
  61. data/lib/lighthouse/where_conditions/operator_map.rb +35 -0
  62. data/lib/lighthouse-graphql.rb +10 -0
  63. metadata +195 -0
@@ -0,0 +1,298 @@
1
+ # Directives Reference
2
+
3
+ Directives are how you add behavior to your schema. They begin with `@` and are
4
+ attached to fields or arguments. Lighthouse **contributes the SDL definitions for
5
+ its own directives automatically**, so you don't have to declare
6
+ `directive @all(...) on ...` yourself — just use them.
7
+
8
+ Below is every built-in directive, grouped by purpose. Federation directives are
9
+ documented separately in [federation.md](federation.md).
10
+
11
+ ---
12
+
13
+ ## Query / fetch directives
14
+
15
+ These resolve a root (or any) field from a model.
16
+
17
+ ### `@all`
18
+
19
+ Return all records of a model.
20
+
21
+ ```graphql
22
+ type Query {
23
+ users: [User!]! @all
24
+ }
25
+ ```
26
+
27
+ The model defaults to the field's return type (`User`). Override with
28
+ `@all(model: "Account")`. Combine with [argument filters](filtering-and-ordering.md)
29
+ and [`@orderBy`](#orderby) to constrain the result.
30
+
31
+ ### `@find`
32
+
33
+ Return a single record, typically by id. Pair it with an argument filter such as
34
+ [`@eq`](#eq) so Lighthouse knows what to match on.
35
+
36
+ ```graphql
37
+ type Query {
38
+ user(id: ID! @eq): User @find
39
+ }
40
+ ```
41
+
42
+ ### `@first`
43
+
44
+ Like `@all`, but returns the **first** matching record instead of a list. Useful
45
+ with filters to fetch one record by criteria.
46
+
47
+ ```graphql
48
+ type Query {
49
+ latestPost(authorId: ID! @eq): Post @first
50
+ }
51
+ ```
52
+
53
+ ### `@paginate`
54
+
55
+ Return a paginated list. Lighthouse rewrites the field to a Paginator type and
56
+ adds pagination arguments.
57
+
58
+ ```graphql
59
+ type Query {
60
+ posts: [Post!]! @paginate
61
+ }
62
+ ```
63
+
64
+ becomes, in effect:
65
+
66
+ ```graphql
67
+ type Query {
68
+ posts(first: Int, after: String): PostConnection
69
+ }
70
+ ```
71
+
72
+ Use `@paginate(type: "CONNECTION")` for Relay-style cursor connections, or the
73
+ default offset-based paginator otherwise.
74
+
75
+ ### `@count`
76
+
77
+ Return the number of records — of a model, or of a relation on the parent object.
78
+
79
+ ```graphql
80
+ type Query {
81
+ userCount: Int! @count(model: "User")
82
+ }
83
+
84
+ type Account {
85
+ usersCount: Int! @count(relation: "users")
86
+ }
87
+ ```
88
+
89
+ ### `@aggregate`
90
+
91
+ Compute an aggregate (`SUM`, `AVG`, `MIN`, `MAX`, `COUNT`) over a column.
92
+
93
+ ```graphql
94
+ type Account {
95
+ totalRevenue: Float @aggregate(relation: "orders", column: "amount", function: SUM)
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Relationship directives
102
+
103
+ Resolve ActiveRecord associations, batched through a Dataloader to avoid N+1.
104
+ See [relationships.md](relationships.md) for the full guide.
105
+
106
+ | Directive | Association |
107
+ | --- | --- |
108
+ | `@hasMany` | one-to-many (returns a connection) |
109
+ | `@hasOne` | one-to-one |
110
+ | `@belongsTo` | inverse of has-one/has-many |
111
+ | `@belongsToMany` | many-to-many |
112
+
113
+ ```graphql
114
+ type User {
115
+ posts: [Post!]! @hasMany
116
+ profile: Profile @hasOne
117
+ account: Account! @belongsTo
118
+ roles: [Role!]! @belongsToMany
119
+ }
120
+ ```
121
+
122
+ The relation name defaults to the field name (snake_cased). Override with
123
+ `@hasMany(relation: "authored_posts")`.
124
+
125
+ ---
126
+
127
+ ## Field directives
128
+
129
+ ### `@field`
130
+
131
+ Delegate a field to a custom resolver class — your escape hatch for anything the
132
+ built-in directives don't cover.
133
+
134
+ ```graphql
135
+ type Contact {
136
+ person: Person @field(resolver: "Resolvers::ContactPerson")
137
+ }
138
+ ```
139
+
140
+ The resolver is resolved against your configured
141
+ [`resolver_namespaces`](configuration.md). It may be a class with a class-level
142
+ `call`, an instance `#call`, or an instance `#resolve`, each receiving
143
+ `(object, args, context)`:
144
+
145
+ ```ruby
146
+ module Resolvers
147
+ class ContactPerson
148
+ def call(contact, _args, _ctx)
149
+ contact.linked_person
150
+ end
151
+ end
152
+ end
153
+ ```
154
+
155
+ ### `@rename`
156
+
157
+ Read a differently-named attribute off the parent object. This is the explicit
158
+ override of the default camelCase→snake_case reader.
159
+
160
+ ```graphql
161
+ type User {
162
+ displayName: String @rename(attribute: "full_name")
163
+ }
164
+ ```
165
+
166
+ ### `@method`
167
+
168
+ Resolve a field by calling a method on the parent object (PHP-parity). Great for
169
+ exposing a computed method or predicate without a custom resolver.
170
+
171
+ ```graphql
172
+ type Conversation {
173
+ labels: [String!]! @method(name: "label_list")
174
+ canReply: Boolean @method(name: "can_reply?")
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Authentication & Authorization
181
+
182
+ Enforce auth declaratively instead of in resolvers. Full guide:
183
+ [authorization.md](authorization.md).
184
+
185
+ ### `@guard`
186
+
187
+ Require an authenticated user before the field resolves; otherwise raise
188
+ `Unauthenticated.`. Runs outermost.
189
+
190
+ ```graphql
191
+ type Query {
192
+ me: User @field(resolver: "Resolvers::CurrentUser") @guard
193
+ }
194
+ ```
195
+
196
+ ### `@can`
197
+
198
+ Authorize a field with a policy ability (Pundit by default; configurable). By
199
+ default it authorizes the resolved record. `repeatable`.
200
+
201
+ ```graphql
202
+ type Query {
203
+ contact(id: ID!): Contact
204
+ @field(resolver: "Resolvers::ContactResolver")
205
+ @can(ability: "show")
206
+ }
207
+ ```
208
+
209
+ `@can(ability:, find:, model:, root:)` — see [authorization.md](authorization.md)
210
+ for the `find`/`model`/`root` modes.
211
+
212
+ ### `@auth`
213
+
214
+ The original guard directive — require an authenticated user
215
+ (`context[:current_user]`) before the field resolves; otherwise raise
216
+ `Unauthenticated.`. `@guard` is the preferred, configurable equivalent.
217
+
218
+ ```graphql
219
+ type Query {
220
+ me: User @auth
221
+ }
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Query builder customization
227
+
228
+ ### `@builder`
229
+
230
+ Apply a custom method to the query builder before the field resolves. Repeatable.
231
+
232
+ ```graphql
233
+ type Query {
234
+ contacts: [Contact!]! @paginate @builder(class: "Resolvers::ContactsBuilder")
235
+ }
236
+ ```
237
+
238
+ ```ruby
239
+ module Resolvers
240
+ class ContactsBuilder
241
+ def self.call(relation, args, _ctx)
242
+ args[:unassigned] ? relation.left_joins(:assignments).where(assignments: { id: nil }) : relation
243
+ end
244
+ end
245
+ end
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Argument filters
251
+
252
+ Attach these to a **field argument** to compose a `WHERE`/`ORDER BY` clause onto
253
+ the query when the client supplies that argument. They compose in argument order.
254
+ Full guide: [filtering-and-ordering.md](filtering-and-ordering.md).
255
+
256
+ | Directive | Effect |
257
+ | --- | --- |
258
+ | `@eq` | `column = value` |
259
+ | `@neq` | `column != value` |
260
+ | `@in` | `column IN (values)` |
261
+ | `@notIn` | `column NOT IN (values)` |
262
+ | `@like` | `column LIKE %value%` |
263
+ | `@where(operator:)` | generic operator (`GT`, `GTE`, `IN`, `IS_NULL`, `CONTAINS`, …) |
264
+ | `@orderBy` | sort by one or more columns |
265
+
266
+ ```graphql
267
+ type Query {
268
+ users(
269
+ status: String @eq
270
+ nameSearch: String @like
271
+ minAge: Int @where(operator: "GTE", key: "age")
272
+ orderBy: [OrderByClause!] @orderBy
273
+ ): [User!]! @all
274
+ }
275
+ ```
276
+
277
+ The column defaults to the snake_cased argument name; override with `key:`.
278
+
279
+ ### `@whereConditions` / `@whereHasConditions`
280
+
281
+ Give clients a structured, dynamic `WHERE` builder (AND/OR/operators, and HAS for
282
+ related records). Lighthouse generates the input and enum types for you. See
283
+ [filtering-and-ordering.md](filtering-and-ordering.md#complex-where-conditions).
284
+
285
+ ```graphql
286
+ type Query {
287
+ people(where: _ @whereConditions(columns: ["age", "type"])): [Person!]! @all
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Adding your own
294
+
295
+ Every directive above is a small class registered with
296
+ `Lighthouse::DirectiveRegistry`. To add one, implement the relevant
297
+ [contract](custom-directives.md) and register it — no edits to Lighthouse
298
+ internals. See [custom-directives.md](custom-directives.md).
@@ -0,0 +1,108 @@
1
+ # Apollo Federation
2
+
3
+ Lighthouse lets a Rails service act as an [Apollo Federation](https://www.apollographql.com/docs/federation/)
4
+ **subgraph**: it understands the federation directives in your SDL and wires up
5
+ entity resolution so a gateway can compose your types with other subgraphs.
6
+
7
+ This relies on the [`apollo-federation`](https://github.com/Gusto/apollo-federation-ruby)
8
+ gem, which you include in your app's base types and schema.
9
+
10
+ ## Setup
11
+
12
+ Include the federation mixins in the base types you pass to Lighthouse, and make
13
+ your schema a federation schema:
14
+
15
+ ```ruby
16
+ class BaseObject < GraphQL::Schema::Object
17
+ include ApolloFederation::Object
18
+
19
+ # Default entity resolution: look the record up by id on the model that shares
20
+ # the type's GraphQL name. Lighthouse falls back to this when no custom
21
+ # resolver is registered (see below).
22
+ def self.resolve_reference(reference, _context)
23
+ model = graphql_name.safe_constantize
24
+ return nil unless model&.respond_to?(:find_by)
25
+
26
+ model.find_by(id: reference[:id] || reference['id'])
27
+ end
28
+ end
29
+
30
+ BaseAppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
31
+ sdl_folder: Rails.root.join('graphql').to_s,
32
+ options: { federation: true, base_types: { object: BaseObject } }
33
+ )
34
+
35
+ class AppSchema < BaseAppSchema
36
+ include ApolloFederation::Schema
37
+ federation(version: '2.3')
38
+ query(superclass.query)
39
+ end
40
+ ```
41
+
42
+ ## Declaring entities
43
+
44
+ Mark a type as a federation entity with `@key`. Lighthouse recognizes the
45
+ federation directives during the resolution phase and applies them:
46
+
47
+ ```graphql
48
+ type User @key(fields: "id") {
49
+ id: ID!
50
+ name: String
51
+ }
52
+
53
+ type Order @key(fields: "id") @shareable {
54
+ id: ID!
55
+ }
56
+ ```
57
+
58
+ Supported object-level directives: `@key`, `@extends`, `@shareable`,
59
+ `@inaccessible`. Field-level `@external` is recognized for federation composition.
60
+
61
+ ## Entity resolution (`resolve_reference`)
62
+
63
+ When the gateway asks your subgraph to resolve an entity (via `_entities`),
64
+ Lighthouse routes to a `resolve_reference` it installs on each `@key` type. The
65
+ order of precedence is:
66
+
67
+ 1. **A custom resolver** registered with `Lighthouse::ReferenceResolver` for that
68
+ type, if any.
69
+ 2. **The base type's `resolve_reference`** otherwise (e.g. the default
70
+ model-lookup-by-id shown above).
71
+
72
+ This means direct-model entities resolve with **zero extra code** — the default
73
+ lookup handles them — while you register custom resolvers only for the entities
74
+ that need bespoke loading.
75
+
76
+ ### Custom reference resolvers
77
+
78
+ For entities that aren't a straightforward `find_by(id:)` — for example, ones
79
+ backed by a join or an external identifier — register a resolver. The single best
80
+ place is an initializer:
81
+
82
+ ```ruby
83
+ # config/initializers/federation.rb
84
+ Rails.application.config.to_prepare do
85
+ Lighthouse::ReferenceResolver.register('JudgmentCreditor') do |reference, _context|
86
+ id = reference['id'] || reference[:id]
87
+ OpenStruct.new(id: id, contacts: Contact.joins(:links).where(links: { external_id: id }))
88
+ end
89
+ end
90
+ ```
91
+
92
+ `register(typename) { |reference, context| ... }` returns the object that
93
+ represents the entity; its fields then resolve normally.
94
+
95
+ ## The federation subgraph SDL
96
+
97
+ A federation subgraph publishes its capabilities via the `_service { sdl }` field
98
+ (and `federation_sdl`), which a gateway uses to compose the supergraph. With the
99
+ setup above this is produced for you, including the `@link` to the federation spec
100
+ and the `@key`/`@shareable`/… directives on your types.
101
+
102
+ ## Gateways
103
+
104
+ Apollo Gateways that use `IntrospectAndCompose` poll subgraphs and recompose
105
+ automatically when your subgraph SDL changes — no manual step needed when you
106
+ deploy schema changes. Gateways that consume a static, pre-composed supergraph
107
+ file need that file recomposed (e.g. via `rover supergraph compose`) after you
108
+ change the subgraph.
@@ -0,0 +1,128 @@
1
+ # Filtering & Ordering
2
+
3
+ Lighthouse offers two complementary ways to constrain a query: **simple argument
4
+ filters** (one directive per argument) and **complex where conditions** (a single
5
+ structured argument the client drives dynamically). Both work on any field backed
6
+ by a query — `@all`, `@paginate`, `@first`, `@count`, and the relationship
7
+ directives.
8
+
9
+ ## Simple argument filters
10
+
11
+ Attach a filter directive to a field argument. When the client passes that
12
+ argument, Lighthouse folds the corresponding clause onto the query builder.
13
+ Filters compose in the order the arguments appear in the SDL.
14
+
15
+ ```graphql
16
+ type Query {
17
+ users(
18
+ status: String @eq
19
+ role: [String!] @in
20
+ nameSearch: String @like
21
+ minAge: Int @where(operator: "GTE", key: "age")
22
+ ): [User!]! @all
23
+ }
24
+ ```
25
+
26
+ | Directive | SQL |
27
+ | --- | --- |
28
+ | `@eq` | `column = value` |
29
+ | `@neq` | `column != value` |
30
+ | `@in` | `column IN (...)` |
31
+ | `@notIn` | `column NOT IN (...)` |
32
+ | `@like` | `column LIKE '%value%'` |
33
+ | `@where(operator:)` | operator-driven (see below) |
34
+
35
+ By default the column is the **snake_cased argument name** (`minAge` → `min_age`).
36
+ Override it with `key:` — above, `minAge` filters the `age` column.
37
+
38
+ ### `@where` operators
39
+
40
+ `@where(operator: "...")` supports the same operator vocabulary as
41
+ `@whereConditions`:
42
+
43
+ `EQ`, `NEQ`, `GT`, `GTE`, `LT`, `LTE`, `IN`, `NIN`, `CONTAINS`, `STARTS_WITH`,
44
+ `ENDS_WITH`, `IS_NULL`, `IS_NOT_NULL`.
45
+
46
+ ```graphql
47
+ type Query {
48
+ events(
49
+ after: String @where(operator: "GTE", key: "starts_at")
50
+ titleContains: String @where(operator: "CONTAINS", key: "title")
51
+ ): [Event!]! @all
52
+ }
53
+ ```
54
+
55
+ ## Ordering — `@orderBy`
56
+
57
+ Sort the result by one or more columns. The client passes a list of clauses, each
58
+ with a `column` and an `order` (`ASC`/`DESC`); clauses apply in order.
59
+
60
+ ```graphql
61
+ type Query {
62
+ users(orderBy: [OrderByClause!] @orderBy): [User!]! @all
63
+
64
+ # query:
65
+ # users(orderBy: [{ column: NAME, order: ASC }, { column: CREATED_AT, order: DESC }])
66
+ }
67
+ ```
68
+
69
+ `@orderBy` also accepts `{ field:, direction: }` clause shapes, so it slots into
70
+ existing client conventions.
71
+
72
+ ## Complex where conditions
73
+
74
+ For a flexible, client-controlled `WHERE` builder, use `@whereConditions`.
75
+ Lighthouse generates the input and column-enum types automatically — you only
76
+ declare the allowed columns.
77
+
78
+ ```graphql
79
+ type Query {
80
+ people(
81
+ where: _ @whereConditions(columns: ["age", "type", "height"])
82
+ ): [Person!]! @all
83
+ }
84
+ ```
85
+
86
+ Clients can then build dynamic conditions with `AND` / `OR` / operators:
87
+
88
+ ```graphql
89
+ {
90
+ people(
91
+ where: {
92
+ AND: [
93
+ { column: AGE, operator: GT, value: 37 }
94
+ { OR: [
95
+ { column: TYPE, value: "Actor" }
96
+ { column: HEIGHT, operator: GTE, value: 150 }
97
+ ] }
98
+ ]
99
+ }
100
+ ) { name }
101
+ }
102
+ ```
103
+
104
+ The generated `SQLOperator` enum covers `EQ`, `NEQ`, `GT`, `GTE`, `LT`, `LTE`,
105
+ `IN`, `NIN`, `CONTAINS`, `STARTS_WITH`, `ENDS_WITH`, `IS_NULL`, `IS_NOT_NULL`.
106
+
107
+ ### `@whereHasConditions`
108
+
109
+ Filter by the existence of a related record, with conditions applied to the
110
+ relation:
111
+
112
+ ```graphql
113
+ type Query {
114
+ users(
115
+ hasPosts: _ @whereHasConditions(columns: ["status"])
116
+ ): [User!]! @all
117
+ }
118
+ ```
119
+
120
+ The relation is inferred from the `has<Relation>` argument name (`hasPosts` →
121
+ `posts`), or specified with `relation:`.
122
+
123
+ ## How columns are resolved
124
+
125
+ Column names from enums (e.g. `CREATED_AT`) and arguments (`minAge`) are mapped to
126
+ real columns through one central rule (`Lighthouse::Support::Naming`): ALLCAPS
127
+ enum keys are downcased, and camelCase is converted to snake_case. This keeps
128
+ client-facing GraphQL idiomatic while matching your Rails schema.
@@ -0,0 +1,152 @@
1
+ # Getting Started
2
+
3
+ This guide takes you from an empty Rails app to a working, directive-driven
4
+ GraphQL endpoint.
5
+
6
+ ## Install
7
+
8
+ ```ruby
9
+ # Gemfile
10
+ gem 'lighthouse-graphql'
11
+ ```
12
+
13
+ ```shell
14
+ bundle install
15
+ ```
16
+
17
+ Runtime dependencies (`graphql ~> 2.5`, `activesupport`) come with the gem. If you
18
+ want federation, add `apollo-federation` to your app — it is intentionally **not**
19
+ a hard dependency of Lighthouse so non-federated apps stay lean.
20
+
21
+ ## 1. Write your schema (SDL)
22
+
23
+ By default Lighthouse loads every `*.graphql` file under `RAILS_ROOT/graphql`,
24
+ sorted by path and concatenated. Split your schema however you like:
25
+
26
+ ```graphql
27
+ # graphql/schema.graphql
28
+ type Query {
29
+ users: [User!]! @all
30
+ user(id: ID! @eq): User @find
31
+ }
32
+
33
+ type User {
34
+ id: ID!
35
+ name: String
36
+ email: String
37
+ }
38
+ ```
39
+
40
+ `@all` resolves to `User.all`; `@find` resolves a single `User` by the `id`
41
+ argument (the `@eq` makes `id` a filter). Lighthouse infers the model from the
42
+ field's return type name (`User`), or you can be explicit with
43
+ `@all(model: "User")`.
44
+
45
+ ## 2. Provide your base types
46
+
47
+ Lighthouse builds on graphql-ruby, so you supply the base classes it should use
48
+ for objects, interfaces, etc. — exactly the place to mix in Apollo Federation or
49
+ any custom field/argument behavior.
50
+
51
+ ```ruby
52
+ # app/graphql/base_object.rb
53
+ class BaseObject < GraphQL::Schema::Object
54
+ # include ApolloFederation::Object # if you use federation
55
+ end
56
+ ```
57
+
58
+ ## 3. Build the schema
59
+
60
+ ```ruby
61
+ # app/graphql/app_schema.rb
62
+ require 'lighthouse-graphql'
63
+
64
+ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
65
+ sdl_folder: Rails.root.join('graphql').to_s,
66
+ options: {
67
+ base_types: {
68
+ object: BaseObject,
69
+ # interface:, union:, enum:, input_object:, scalar: ...
70
+ }
71
+ }
72
+ )
73
+ ```
74
+
75
+ `get_schema` returns a `GraphQL::Schema` subclass you can use directly, or
76
+ subclass further (e.g. to add federation, tracing, a Dataloader):
77
+
78
+ ```ruby
79
+ class AppSchema < AppSchema # or build a Base*Schema and subclass it
80
+ use GraphQL::Dataloader
81
+ end
82
+ ```
83
+
84
+ > Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads. Add
85
+ > `use GraphQL::Dataloader` to your schema to get N+1-free relationships.
86
+
87
+ ## 4. Serve it
88
+
89
+ A standard graphql-ruby controller:
90
+
91
+ ```ruby
92
+ class GraphqlController < ApplicationController
93
+ def execute
94
+ result = AppSchema.execute(
95
+ params[:query],
96
+ variables: params[:variables],
97
+ context: { current_user: current_user },
98
+ operation_name: params[:operationName]
99
+ )
100
+ render json: result
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## 5. Configure (optional)
106
+
107
+ Create an initializer to wire app-specific settings. This is also the single
108
+ place to register custom directives and federation reference resolvers, which
109
+ keeps Lighthouse itself free of any app constants.
110
+
111
+ ```ruby
112
+ # config/initializers/lighthouse.rb
113
+ require 'lighthouse-graphql'
114
+
115
+ Lighthouse.configure do |config|
116
+ # Namespaces tried when resolving a class referenced by a directive argument,
117
+ # e.g. @field(resolver: "Users::Profile") or @all(model: "User").
118
+ config.resolver_namespaces = ['App::Graphql', 'Resolvers']
119
+
120
+ # How swallowed directive errors are surfaced (raise in test, log otherwise).
121
+ # config.on_error = ->(error, _ctx) { Sentry.capture_exception(error) }
122
+ end
123
+ ```
124
+
125
+ See [configuration.md](configuration.md) for all options.
126
+
127
+ ## Casing: camelCase ↔ snake_case
128
+
129
+ GraphQL conventionally uses `camelCase` field names; Rails uses `snake_case`
130
+ attributes. Lighthouse bridges them with one rule: a field with no directive is
131
+ read off the parent object by its **snake_cased** name.
132
+
133
+ ```graphql
134
+ type User {
135
+ customAttributes: String # reads user.custom_attributes
136
+ }
137
+ ```
138
+
139
+ When the mapping isn't a simple `underscore`, use [`@rename`](directives.md#rename):
140
+
141
+ ```graphql
142
+ type User {
143
+ displayName: String @rename(attribute: "full_name")
144
+ }
145
+ ```
146
+
147
+ ## Next steps
148
+
149
+ - [Directives reference](directives.md) — the full vocabulary.
150
+ - [Relationships](relationships.md) — wiring associations.
151
+ - [Filtering & ordering](filtering-and-ordering.md) — query constraints.
152
+ - [Apollo Federation](federation.md) — become a subgraph.