graphql-relay 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea4c2cc57f963c64ecb34d90f53e32e943faf1bc
|
4
|
+
data.tar.gz: a520db57682116bd3f93d5db075d41f88320ca33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68bfb22e922e5fd87d65c7a42731c559fe02faac3c9c05acfb3f065c6d3325ae60bd8bab133677a2188b03caeadf7069c17ddcc1d0ae2ad774bd025b97e981fc
|
7
|
+
data.tar.gz: 34475ee06278d2f5b814d874db45ec0150631811a98e50edddee7401c436f9b6e873936e2f75188779cdd45dfc8a43a6da798c02261b0dbbf270cbb21ea371a2
|
data/README.md
CHANGED
@@ -19,60 +19,284 @@ bundle install
|
|
19
19
|
|
20
20
|
## Usage
|
21
21
|
|
22
|
+
`graphql-relay` provides several helpers for making a Relay-compliant GraphQL endpoint in Ruby:
|
23
|
+
|
24
|
+
- [global ids](#global-ids) support Relay's UUID-based refetching
|
25
|
+
- [connections](#connections) implement Relay's pagination
|
26
|
+
- [mutations](#mutations) allow Relay to mutate your system predictably
|
27
|
+
|
28
|
+
|
22
29
|
### Global Ids
|
23
30
|
|
24
|
-
Global
|
31
|
+
Global ids (or UUIDs) provide refetching & global identification for Relay.
|
32
|
+
|
33
|
+
#### UUID Lookup
|
34
|
+
|
35
|
+
Use `GraphQL::Relay::GlobalNodeIdentification` helper by defining `object_from_id(global_id, ctx)` & `type_from_object(object)`. The resulting `NodeIdentification` object is in your schema _and_ internally by `GraphQL::Relay`.
|
36
|
+
|
25
37
|
|
26
|
-
|
38
|
+
```ruby
|
39
|
+
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
|
40
|
+
# Given a UUID & the query context,
|
41
|
+
# return the corresponding application object
|
42
|
+
object_from_id -> (id, ctx) do
|
43
|
+
type_name, id = NodeIdentification.from_global_id(id)
|
44
|
+
# "Post" -> Post.find(id)
|
45
|
+
Object.const_get(type_name).find(id)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Given an application object,
|
49
|
+
# return a GraphQL ObjectType to expose that object
|
50
|
+
type_from_object -> (object) do
|
51
|
+
if object.is_a?(Post)
|
52
|
+
PostType
|
53
|
+
else
|
54
|
+
CommentType
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
27
59
|
|
28
|
-
|
60
|
+
#### UUID fields
|
29
61
|
|
30
|
-
|
62
|
+
ObjectTypes in your schema should implement `NodeIdentification.interface` with the `global_id_field` helper, for example:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
PostType = GraphQL::ObjectType.define do
|
66
|
+
name "Post"
|
67
|
+
interfaces [NodeIdentification.interface]
|
68
|
+
# `id` exposes the UUID
|
69
|
+
global_id_field :id
|
70
|
+
|
71
|
+
# ...
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
#### `node` field (find-by-UUID)
|
76
|
+
|
77
|
+
You should also add a field to your root query type for Relay to re-fetch objects:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
QueryType = GraphQL::ObjectType.define do
|
81
|
+
name "Query"
|
82
|
+
# Used by Relay to lookup objects by UUID:
|
83
|
+
field :node, field: NodeIdentification.field
|
84
|
+
|
85
|
+
# ...
|
86
|
+
end
|
87
|
+
```
|
31
88
|
|
32
89
|
### Connections
|
33
90
|
|
34
|
-
Connections
|
91
|
+
Connections provide pagination and `pageInfo` for `Array`s or `ActiveRecord::Relation`s.
|
92
|
+
|
93
|
+
#### Connection fields
|
94
|
+
|
95
|
+
To define a connection field, use the `connection` helper. For a return type, get a type's `.connection_type`. For example:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
PostType = GraphQL::ObjectType.define do
|
99
|
+
# `comments` field returns a CommentsConnection:
|
100
|
+
connection :comments, CommentType.connection_type
|
101
|
+
# To avoid circular dependencies, wrap the return type in a proc:
|
102
|
+
connection :similarPosts, -> { PostType.connection_type }
|
103
|
+
|
104
|
+
# ...
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
You can also define custom arguments and a custom resolve function for connections, just like other fields:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
connection :featured_comments, CommentType.connection_type do
|
112
|
+
# Add an argument:
|
113
|
+
argument :since, types.String
|
114
|
+
|
115
|
+
# Return an Array or ActiveRecord::Relation
|
116
|
+
resolve -> (post, args, ctx) {
|
117
|
+
comments = post.comments.featured
|
35
118
|
|
36
|
-
|
37
|
-
|
38
|
-
|
119
|
+
if args[:since]
|
120
|
+
comments = comments.where("created_at >= ", since)
|
121
|
+
end
|
39
122
|
|
40
|
-
|
123
|
+
comments
|
124
|
+
}
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
#### Connection types
|
129
|
+
|
130
|
+
You can customize a connection type with `.define_connection`:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
PostType.define_connection do
|
134
|
+
field :totalCount do
|
135
|
+
type types.Int
|
136
|
+
# `obj` is the Connection, `obj.object` is the collection of Posts
|
137
|
+
resolve -> (obj, args, ctx) { obj.object.count }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
41
141
|
|
42
|
-
|
43
|
-
- [Connection with a different name than the underlying property](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L77)
|
142
|
+
Now, `PostType.connection_type` will include a `totalCount` field.
|
44
143
|
|
45
|
-
|
144
|
+
#### Connection objects
|
145
|
+
|
146
|
+
Maybe you need to make a connection object yourself (for example, to return a connection type from a mutation). You can create a connection object like this:
|
147
|
+
|
148
|
+
```
|
149
|
+
items = ... # your collection objects
|
150
|
+
args = {} # stub out arguments for this connection object
|
151
|
+
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
|
152
|
+
connection_class.new(items, args)
|
153
|
+
```
|
154
|
+
|
155
|
+
`.connection_for_items` will return RelationConnection or ArrayConnection depending on `items`, then you can make a new connection
|
156
|
+
|
157
|
+
#### Custom connections
|
158
|
+
|
159
|
+
You can define a custom connection class and add it to `GraphQL::Relay`.
|
160
|
+
|
161
|
+
First, define the custom connection:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
class SetConnection < BaseConnection
|
165
|
+
# derive a cursor from `item`
|
166
|
+
# (it is used to find next & previous nodes,
|
167
|
+
# so it should include `order`)
|
168
|
+
def cursor_from_node(item)
|
169
|
+
# ...
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
# apply `#first` & `#last` to limit results
|
174
|
+
def paged_nodes
|
175
|
+
# ...
|
176
|
+
end
|
177
|
+
|
178
|
+
# apply cursor, order, filters, etc
|
179
|
+
# to get a subset of matching objects
|
180
|
+
def sliced_nodes
|
181
|
+
# ...
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
Then, register the new connection with `GraphQL::Relay::BaseConnection`:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
# When exposing a `Set`, use `SetConnection`:
|
190
|
+
GraphQL::Relay::BaseConnection.register_connection_implementation(Set, SetConnection)
|
191
|
+
```
|
192
|
+
|
193
|
+
At runtime, `GraphQL::Relay` will use `SetConnection` to expose `Set`s.
|
194
|
+
|
195
|
+
#### Creating connection fields by hand
|
196
|
+
|
197
|
+
If you need lower-level access to Connection fields, you can create them programmatically. Given a `GraphQL::Field` which returns a collection of items, you can turn it into a connection field with `ConnectionField.create`.
|
198
|
+
|
199
|
+
For example, to wrap a field with a connection field:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
field = GraphQL::Field.new
|
203
|
+
# ... define the field
|
204
|
+
connection_field = GraphQL::Relay::ConnectionField.create(field)
|
205
|
+
```
|
46
206
|
|
47
|
-
At runtime, `graphql-relay` chooses a connection implementation to expose a collection. `graphql-relay` provides `ArrayConnection` and `RelationConnection`, but you can also provide your own with `BaseConnection.register_connection_implementation`.
|
48
207
|
|
49
208
|
### Mutations
|
50
209
|
|
51
|
-
Mutations allow Relay to mutate your system.
|
52
|
-
|
53
|
-
|
54
|
-
|
210
|
+
Mutations allow Relay to mutate your system. They conform to a strict API which makes them predictable to the client.
|
211
|
+
|
212
|
+
### Mutation root
|
213
|
+
|
214
|
+
To add mutations to your GraphQL schema, define a mutation type and pass it to your schema:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
# Define the mutation type
|
218
|
+
MutationType = GraphQL::ObjectType.define do
|
219
|
+
name "Mutation"
|
220
|
+
# ...
|
221
|
+
end
|
222
|
+
|
223
|
+
# and pass it to the schema
|
224
|
+
MySchema = GraphQL::Schema.new(
|
225
|
+
query: QueryType,
|
226
|
+
mutation: MutationType
|
227
|
+
)
|
228
|
+
```
|
229
|
+
|
230
|
+
Like `QueryType`, `MutationType` is a root of the schema.
|
55
231
|
|
56
|
-
|
232
|
+
### Mutation fields
|
233
|
+
|
234
|
+
Members of `MutationType` are _mutation fields_. For GraphQL in general, mutation fields are identical to query fields _except_ that they have side-effects (which mutate application state, eg, update the database).
|
235
|
+
|
236
|
+
For Relay-compliant GraphQL, a mutation field must comply to a strict API. `GraphQL::Relay` includes a mutation definition helper (see below) to make it simple.
|
237
|
+
|
238
|
+
After defining a mutation (see below), add it to your mutation type:
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
MutationType = GraphQL::ObjectType.define do
|
242
|
+
name "Mutation"
|
243
|
+
# Add the mutation's derived field to the mutation type
|
244
|
+
field :addComment, field: AddCommentMutation.field
|
245
|
+
# ...
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
### Relay mutations
|
57
250
|
|
58
251
|
To define a mutation, use `GraphQL::Relay::Mutation.define`. Inside the block, you should configure:
|
59
252
|
- `name`, which will name the mutation field & derived types
|
60
253
|
- `input_field`s, which will be applied to the derived `InputObjectType`
|
61
254
|
- `return_field`s, which will be applied to the derived `ObjectType`
|
62
|
-
- `resolve(-> (inputs, ctx))`, the mutation which will actually happen
|
255
|
+
- `resolve(-> (inputs, ctx) { ... })`, the mutation which will actually happen
|
256
|
+
|
257
|
+
|
258
|
+
For example:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
AddCommentMutation = GraphQL::Relay::Mutation.define do
|
262
|
+
# Used to name derived types:
|
263
|
+
name "AddComment"
|
264
|
+
|
265
|
+
# Accessible from `input` in the resolve function:
|
266
|
+
input_field :postId, !types.ID
|
267
|
+
input_field :authorId, !types.ID
|
268
|
+
input_field :content, !types.String
|
269
|
+
|
270
|
+
# The result has access to these fields,
|
271
|
+
# resolve must return a hash with these keys
|
272
|
+
return_field :post, PostType
|
273
|
+
return_field :comment, CommentType
|
274
|
+
|
275
|
+
# The resolve proc is where you alter the system state.
|
276
|
+
resolve -> (inputs, ctx) {
|
277
|
+
post = Post.find(inputs[:postId])
|
278
|
+
comment = post.comments.create!(author_id: inputs[:authorId], content: inputs[:content])
|
279
|
+
|
280
|
+
{comment: comment, post: post}
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
```
|
286
|
+
|
287
|
+
Under the hood, GraphQL creates:
|
288
|
+
- A field for your schema's `mutation` root
|
289
|
+
- A derived `InputObjectType` for input values
|
290
|
+
- A derived `ObjectType` for return values
|
63
291
|
|
64
292
|
The resolve proc:
|
65
293
|
- Takes `inputs`, which is a hash whose keys are the ones defined by `input_field`
|
66
294
|
- Takes `ctx`, which is the query context you passed with the `context:` keyword
|
67
295
|
- Must return a hash with keys matching your defined `return_field`s
|
68
296
|
|
69
|
-
Examples:
|
70
|
-
- Definition: [example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L90)
|
71
|
-
- Mount on mutation type: [example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L127)
|
72
297
|
|
73
298
|
## Todo
|
74
299
|
|
75
|
-
- Show how to wrap a simple field in a connection field with `ConnectionField.create`
|
76
300
|
- Add a `max_page_size` config for connections?
|
77
301
|
- Refactor some RelationConnection issues:
|
78
302
|
- fix [unbounded count in page info](https://github.com/rmosolgo/graphql-relay-ruby/blob/88b3d94f75a6dd4c8b2604743108db31f66f8dcc/lib/graphql/relay/base_connection.rb#L79-L86), [details](https://github.com/rmosolgo/graphql-relay-ruby/issues/1)
|
@@ -7,7 +7,7 @@ module GraphQL
|
|
7
7
|
# which delegate to the singleton instance.
|
8
8
|
class GlobalNodeIdentification
|
9
9
|
class << self
|
10
|
-
|
10
|
+
attr_accessor :id_separator
|
11
11
|
end
|
12
12
|
self.id_separator = "-"
|
13
13
|
|
@@ -59,10 +59,11 @@ module GraphQL
|
|
59
59
|
# Create a global ID for type-name & ID
|
60
60
|
# (This is an opaque transform)
|
61
61
|
def to_global_id(type_name, id)
|
62
|
-
|
62
|
+
id_str = id.to_s
|
63
|
+
if type_name.include?(self.class.id_separator) || id_str.include?(self.class.id_separator)
|
63
64
|
raise "to_global_id(#{type_name}, #{id}) contains reserved characters `#{self.class.id_separator}`"
|
64
65
|
end
|
65
|
-
Base64.strict_encode64([type_name,
|
66
|
+
Base64.strict_encode64([type_name, id_str].join(self.class.id_separator))
|
66
67
|
end
|
67
68
|
|
68
69
|
# Get type-name & ID from global ID
|
@@ -54,7 +54,7 @@ describe GraphQL::Relay::GlobalNodeIdentification do
|
|
54
54
|
|
55
55
|
describe 'to_global_id / from_global_id ' do
|
56
56
|
it 'Converts typename and ID to and from ID' do
|
57
|
-
global_id = node_identification.to_global_id("SomeType",
|
57
|
+
global_id = node_identification.to_global_id("SomeType", 123)
|
58
58
|
type_name, id = node_identification.from_global_id(global_id)
|
59
59
|
assert_equal("SomeType", type_name)
|
60
60
|
assert_equal("123", id)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-relay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|