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: 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