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: 81585136098af15f81a346878783f89731f2b623
4
- data.tar.gz: 1dc5293103fcde26d25f627225e7e35a69c34d00
3
+ metadata.gz: ea4c2cc57f963c64ecb34d90f53e32e943faf1bc
4
+ data.tar.gz: a520db57682116bd3f93d5db075d41f88320ca33
5
5
  SHA512:
6
- metadata.gz: ae47210e0b77604da888d6d9999dbb930f3eb146a8045304dc12d4d494cd9544877abe1702d4baa442856d079497654d246274bfb370251ddeac527daa9f4864
7
- data.tar.gz: dc31f60ea115c6de5092baa4ddcc0f9594dd961d44f9a5c6ff112b04b25cf2ddb51d7ccfa6be244af26f8de796436db10607d473674edbd91861348030ef7b2c
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 Ids provide refetching & global identification for Relay.
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
- You should create `GraphQL::Relay::GlobalNodeIdentification` helper by defining `object_from_id(global_id)` & `type_from_object(object)`. The resulting object provides ID resultion methods, a find-by-global-id field and a node interface. [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L9-L18)
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
- ObjectTypes should implement that interface with the `global_id_field` helper: [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L30-L31)
60
+ #### UUID fields
29
61
 
30
- You should attach the field to your query type: [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L121)
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 will provide arguments, pagination and `pageInfo` for `Array`s or `ActiveRecord::Relation`s. You can use the `connection` definition helper.
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
- Then, implement the field. It's different than a normal field:
37
- - use the `connection` helper to define it, instead of `field`
38
- - Call `#connection_type` on an `ObjectType` for the field's return type (eg, `ShipType.connection_type`)
119
+ if args[:since]
120
+ comments = comments.where("created_at >= ", since)
121
+ end
39
122
 
40
- Examples:
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
- - [Connection with custom arguments](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L51-L63)
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
- You can also add custom fields to connection objects: [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L36-L43)
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. When you define a mutation, you'll be defining:
52
- - A field for your schema's `mutation` root
53
- - A derived `InputObjectType` for input values
54
- - A derived `ObjectType` for return values
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
- You _don't_ define anything having to do with `clientMutationId`. That's automatically created.
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
- attr_accessor :id_separator
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
- if type_name.include?(self.class.id_separator) || id.include?(self.class.id_separator)
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, id].join(self.class.id_separator))
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
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Relay
3
- VERSION = '0.6.0'
3
+ VERSION = '0.6.1'
4
4
  end
5
5
  end
@@ -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", "123")
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.0
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 00:00:00.000000000 Z
11
+ date: 2015-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql