graphql-relay 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
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
|