File: interfaces.md

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (270 lines) | stat: -rw-r--r-- 8,325 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
---
layout: guide
doc_stub: false
search: true
section: Type Definitions
title: Interfaces
desc: Interfaces are lists of fields which objects may implement
index: 4
redirect_from:
  - /types/abstract_types/
---

Interfaces are lists of fields which may be implemented by object types.

An interface has fields, but it's never actually instantiated. Instead, objects may _implement_ interfaces, which makes them a _member_ of that interface. Also, fields may _return_ interface types. When this happens, the returned object may be any member of that interface.

For example, let's say a `Customer` (interface) may be either an `Individual` (object) or a `Company` (object). Here's the structure in the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL):

```graphql
interface Customer {
  name: String!
  outstandingBalance: Int!
}

type Company implements Customer {
  employees: [Individual!]!
  name: String!
  outstandingBalance: Int!
}

type Individual implements Customer {
  company: Company
  name: String!
  outstandingBalance: Int!
}
```

Notice that the `Customer` interface requires two fields, `name: String!` and `outstandingBalance: Int!`. Both `Company` and `Individual` implement those fields, so they can implement `Customer`. Their implementation of `Customer` is made explicit by `implements Customer` in their definition.

When querying, you can get the fields on an interface:

```graphql
{
  customers(first: 5) {
    name
    outstandingBalance
  }
}
```

Whether the objects are `Company` or `Individual`, it doesn't matter -- you still get their `name` and `outstandingBalance`. If you want some object-specific fields, you can query them with an _inline fragment_, for example:

```graphql
{
  customers(first: 5) {
    name
    ... on Individual {
      company { name }
    }
  }
}
```

This means, "if the customer is an `Individual`, also get the customer's company name".

Interfaces are a good choice whenever a set of objects are used interchangeably, and they have several significant fields in common. When they don't have fields in common, use a {% internal_link "Union", "/type_definitions/unions" %} instead.

## Defining Interface Types

Interfaces are Ruby modules which include {{ "GraphQL::Schema::Interface" | api_doc }}. First, make a base module:

```ruby
module Types::BaseInterface
  include GraphQL::Schema::Interface
end
```

Then, include that into each interface:

```ruby
module Types::RetailItem
  include Types::BaseInterface
  description "Something that can be bought"
  field :price, Types::Price, "How much this item costs", null: false

  def price
    # Optional: provide a special implementation of `price` here
  end


  # Optional, see below
  definition_methods do
    # Optional: if this method is defined, it overrides `Schema.resolve_type`
    def resolve_type(object, context)
      # ...
    end
  end
end
```

Interface classes are never instantiated. At runtime, only their `.resolve_type` methods are called (if they're defined).

### Implementing Interfaces

To define object types that implement this interface use the `implements` method:

```ruby
class Types::Car < Types::BaseObject
  implements Types::RetailItem

  # ... additional fields
end

class Types::Purse < Types::BaseObject
  implements Types::RetailItem

  # ... additional fields
end
```

Those object types will _inherit_ field definitions from those interfaces.

If you add an object type which implements an interface, but that object type doesn't appear in your schema as a field return type, a union member, or a root type, then you need to add that object to the interfaces's `orphan_types`.

### Implementing Fields

Interfaces may provide field implementations along with the signatures. For example:

```ruby
field :price, Types::Price, "How much this item costs", null: false

# Implement this field to return a `::Price` object
def price
  ::Price.from_cents(@object.price_in_cents)
end
```

This method will be called by objects who implement the interface. To override this implementation,
object classes can override the `#price` method.

Read more in the {% internal_link "Fields guide", "/fields/introduction" %}.

### Definition Methods

You can use `definition_methods do ... end` to add helper methods to interface modules. By adding methods to `definition_methods`:

- Those methods will be available as class methods in the interface itself
- These class methods will _also_ be added to interfaces that `include` this interface.

This way, class methods are inherited when interfaces `include` other interfaces. (`definition_methods` is like `ActiveSupport::Concern`'s `class_methods` in this regard, but it has a different name to avoid naming conflicts).

For example, you can add definition helpers to your base interface, then use them in concrete interfaces later:

```ruby
# First, add a helper method to `BaseInterface`'s definition methods
module Types::BaseInterface
  include GraphQL::Schema::Interface

  definition_methods do
    # Use this to add a price field + default implementation
    def price_field
      field(:price, ::Types::Price, null: false)
      define_method(:price) do
        ::Price.from_cents(@object.price_in_cents)
      end
    end
  end
end

# Then call it later
module Types::ForSale
  include Types::BaseInterface
  # This calls `price_field` from definition methods
  price_field
end
```

The type definition DSL uses this mechanism, too, so you can override those methods here also.

Note: Under the hood, `definition_methods` causes a module to be `extend`ed by the Inteface. Any calls to `extend` or `implement` may override methods from `definition_methods`.

### Resolve Type

When a field's return type is an interface, GraphQL has to figure out what _specific_ object type to use for the return value. In the example above, each `customer` must be categorized as an `Individual` or `Company`. You can do this by:

- Providing a top-level `Schema.resolve_type` method; _OR_
- Providing an interface-level `.resolve_type` method in `definition_methods`.

This method will be called whenever an object must be disambiguated. For example:

```ruby
module Types::RetailItem
  include Types::BaseInterface
  definition_methods do
    # Determine what object type to use for `object`
    def resolve_type(object, context)
      if object.is_a?(::Car) || object.is_a?(::Truck)
        Types::Car
      elsif object.is_a?(::Purse)
        Types::Purse
      else
        raise "Unexpected RetailItem: #{object.inspect}"
      end
    end
  end
end
```

You can also optionally return a "resolved" object in addition the resolved type by returning an array:

```ruby
module Types::Claim
  include Types::BaseInterface
  definition_methods do
    def resolve_type(object, context)
      type = case object.value
      when Success
        Types::Approved
      when Error
        Types::Rejected
      else
        raise "Unexpected Claim: #{object.inspect}"
      end

      [type, object.value]
    end
  end
end
```

The returned array must be a tuple of `[Type, object]`.
This is useful for interface or union types which are backed by a domain object which should be unwrapped before resolving the next field.

## Orphan Types

If you add an object type which implements an interface, but that object type doesn't properly appear in your schema, then you need to add that object to the interfaces's `orphan_types`, for example:

```ruby
module Types::RetailItem
  include Types::BaseInterface
  # ...
  orphan_types Types::Car
end
```

Alternatively you can add the object types to the schema's `orphan_types`:

```ruby
class MySchema < GraphQL::Schema
  orphan_types Types::Car
end
```

This is required because a schema finds it types by traversing its fields, starting with `query`, `mutation` and `subscription`. If an object is _never_ the return type of a field, but only connected via an interface, then it must be explicitly connected to the schema via `orphan_types`. For example, given this schema:

```graphql
type Query {
  node(id: ID!): Node
}

interface Node {
  id: ID!
}

type Comment implements Node {
  id: ID!
}
```

`Comment` must be added via `orphan_types` since it's never used as the return type of a field. (Only `Node` and `ID` are used as return types.)