graphql-relay 0.10.0 → 0.11.0
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 +4 -4
- data/README.md +111 -6
- data/lib/graphql/relay.rb +2 -0
- data/lib/graphql/relay/base_connection.rb +10 -21
- data/lib/graphql/relay/connection_type.rb +25 -0
- data/lib/graphql/relay/edge.rb +5 -8
- data/lib/graphql/relay/edge_type.rb +14 -0
- data/lib/graphql/relay/monkey_patches/base_type.rb +11 -6
- data/lib/graphql/relay/mutation.rb +3 -3
- data/lib/graphql/relay/relation_connection.rb +7 -0
- data/lib/graphql/relay/version.rb +1 -1
- data/spec/graphql/relay/connection_type_spec.rb +33 -0
- data/spec/graphql/relay/mutation_spec.rb +23 -6
- data/spec/graphql/relay/relation_connection_spec.rb +98 -3
- data/spec/spec_helper.rb +2 -1
- data/spec/support/star_wars_data.rb +8 -2
- data/spec/support/star_wars_schema.rb +40 -4
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9e0b75206216efdc87ad4a9d85f6032d89ea4ec
|
4
|
+
data.tar.gz: a8ed52c2debcd019a00f62f5ebe0eee3ebe17c63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34a25f845c250b77c5f7ecfc31aadbcacf0ac4f4ada1d102cfbeb687d29a4ddd983b0e32cedd20bb87f06d8dceef9099361936c99c56194c9156ce565b8ca674
|
7
|
+
data.tar.gz: bbd6a2a4f2def1b7eb981f9190a1291a5a2ba5999e3590f62c4a33c6c928fa1f1c81dd7edf1f9c4fe06540dc00a1a4116b048faad50f663c1c78d7a8da290f8a
|
data/README.md
CHANGED
@@ -5,7 +5,8 @@
|
|
5
5
|
[](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby)
|
6
6
|
[](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/coverage)
|
7
7
|
|
8
|
-
Helpers for using [`graphql`](https://github.com/rmosolgo/graphql-ruby) with Relay.
|
8
|
+
Helpers for using [`graphql`](https://github.com/rmosolgo/graphql-ruby) with Relay. Includes support for serving Relay connections from `Array`s, `ActiveRecord::Relation`s and `Sequel::Dataset`s.
|
9
|
+
|
9
10
|
|
10
11
|
[API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-relay-ruby)
|
11
12
|
## Installation
|
@@ -115,7 +116,7 @@ end
|
|
115
116
|
|
116
117
|
### Connections
|
117
118
|
|
118
|
-
Connections provide pagination and `pageInfo` for `Array`s or `
|
119
|
+
Connections provide pagination and `pageInfo` for `Array`s, `ActiveRecord::Relation`s or `Sequel::Dataset`s.
|
119
120
|
|
120
121
|
#### Connection fields
|
121
122
|
|
@@ -136,6 +137,9 @@ You can also define custom arguments and a custom resolve function for connectio
|
|
136
137
|
|
137
138
|
```ruby
|
138
139
|
connection :featured_comments, CommentType.connection_type do
|
140
|
+
# Use a name to disambiguate this from `CommentType.connection_type`
|
141
|
+
name "CommentConnectionWithSince"
|
142
|
+
|
139
143
|
# Add an argument:
|
140
144
|
argument :since, types.String
|
141
145
|
|
@@ -165,16 +169,119 @@ connection :featured_comments, CommentType.connection_type, max_page_size: 50
|
|
165
169
|
You can customize a connection type with `.define_connection`:
|
166
170
|
|
167
171
|
```ruby
|
168
|
-
PostType.define_connection do
|
172
|
+
PostConnectionWithTotalCountType = PostType.define_connection do
|
169
173
|
field :totalCount do
|
170
174
|
type types.Int
|
171
175
|
# `obj` is the Connection, `obj.object` is the collection of Posts
|
172
176
|
resolve -> (obj, args, ctx) { obj.object.count }
|
173
177
|
end
|
174
178
|
end
|
179
|
+
|
180
|
+
```
|
181
|
+
|
182
|
+
Now, you can use `PostConnectionWithTotalCountType` to define a connection with the "totalCount" field:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
AuthorType = GraphQL::ObjectType.define do
|
186
|
+
# Use the custom connection type:
|
187
|
+
connection :posts, PostConnectionWithTotalCountType
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
#### Custom edge types
|
192
|
+
|
193
|
+
If you need custom fields on `edge`s, you can define an edge type and pass it to a connection:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# Person => Membership => Team
|
197
|
+
MembershipSinceEdgeType = BaseType.define_edge do
|
198
|
+
name "MembershipSinceEdge"
|
199
|
+
field :memberSince, types.Int, "The date that this person joined this team" do
|
200
|
+
resolve -> (obj, args, ctx) {
|
201
|
+
obj # => GraphQL::Relay::Edge instnce
|
202
|
+
person = obj.parent
|
203
|
+
team = obj.node
|
204
|
+
membership = Membership.where(person: person, team: team).first
|
205
|
+
membership.created_at.to_i
|
206
|
+
}
|
207
|
+
end
|
208
|
+
end
|
175
209
|
```
|
176
210
|
|
177
|
-
|
211
|
+
Then, pass the edge type when defining the connection type:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
TeamMembershipsConnectionType = TeamType.define_connection(edge_type: MembershipSinceEdgeType) do
|
215
|
+
# Use a name so it doesn't conflict with "TeamConnection"
|
216
|
+
name "TeamMembershipsConnection"
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
Now, you can query custom fields on the `edge`:
|
221
|
+
|
222
|
+
```graphql
|
223
|
+
{
|
224
|
+
me {
|
225
|
+
teams {
|
226
|
+
edge {
|
227
|
+
memberSince # <= Here's your custom field
|
228
|
+
node {
|
229
|
+
teamName: name
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
```
|
236
|
+
|
237
|
+
#### Custom Edge classes
|
238
|
+
|
239
|
+
For more robust custom edges, you can define a custom edge class. It will be `obj` in the edge type's resolve function. For example, to define a membership edge:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
# Make sure to familiarize yourself with GraphQL::Relay::Edge --
|
243
|
+
# you have to avoid naming conflicts here!
|
244
|
+
class MembershipSinceEdge < GraphQL::Relay::Edge
|
245
|
+
# Cache `membership` to avoid multiple DB queries
|
246
|
+
def membership
|
247
|
+
@membership ||= begin
|
248
|
+
# "parent" and "node" are passed in from the surrounding Connection,
|
249
|
+
# See `Edge#initialize` for details
|
250
|
+
person = self.parent
|
251
|
+
team = self.node
|
252
|
+
Membership.where(person: person, team: team).first
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def member_since
|
257
|
+
membership.created_at.to_i
|
258
|
+
end
|
259
|
+
|
260
|
+
def leader?
|
261
|
+
membership.leader?
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
Then, hook it up with custom edge type and custom connection type:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# Person => Membership => Team
|
270
|
+
MembershipSinceEdgeType = BaseType.define_edge do
|
271
|
+
name "MembershipSinceEdge"
|
272
|
+
field :memberSince, types.Int, "The date that this person joined this team", property: :member_since
|
273
|
+
field :isPrimary, types.Boolean, "Is this person the team leader?". property: :primary?
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
TeamMembershipsConnectionType = TeamType.define_connection(
|
278
|
+
edge_class: MembershipSinceEdge,
|
279
|
+
edge_type: MembershipSinceEdgeType,
|
280
|
+
) do
|
281
|
+
# Use a name so it doesn't conflict with "TeamConnection"
|
282
|
+
name "TeamMembershipsConnection"
|
283
|
+
end
|
284
|
+
```
|
178
285
|
|
179
286
|
#### Connection objects
|
180
287
|
|
@@ -237,7 +344,6 @@ field = GraphQL::Field.new
|
|
237
344
|
connection_field = GraphQL::Relay::ConnectionField.create(field)
|
238
345
|
```
|
239
346
|
|
240
|
-
|
241
347
|
### Mutations
|
242
348
|
|
243
349
|
Mutations allow Relay to mutate your system. They conform to a strict API which makes them predictable to the client.
|
@@ -342,7 +448,6 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
|
|
342
448
|
|
343
449
|
## Todo
|
344
450
|
|
345
|
-
- Allow custom edge fields (per connection type)
|
346
451
|
- `GlobalNodeIdentification.to_global_id` should receive the type name and _object_, not `id`. (Or, maintain the "`type_name, id` in, `type_name, id` out" pattern?)
|
347
452
|
- Make GlobalId a property of the schema, not a global
|
348
453
|
- Reduce duplication in ArrayConnection / RelationConnection
|
data/lib/graphql/relay.rb
CHANGED
@@ -7,12 +7,14 @@ require 'graphql/relay/define'
|
|
7
7
|
require 'graphql/relay/global_node_identification'
|
8
8
|
require 'graphql/relay/page_info'
|
9
9
|
require 'graphql/relay/edge'
|
10
|
+
require 'graphql/relay/edge_type'
|
10
11
|
require 'graphql/relay/base_connection'
|
11
12
|
require 'graphql/relay/array_connection'
|
12
13
|
require 'graphql/relay/relation_connection'
|
13
14
|
require 'graphql/relay/global_id_field'
|
14
15
|
require 'graphql/relay/mutation'
|
15
16
|
require 'graphql/relay/connection_field'
|
17
|
+
require 'graphql/relay/connection_type'
|
16
18
|
|
17
19
|
# Accept Relay-specific definitions
|
18
20
|
GraphQL::BaseType.accepts_definitions(
|
@@ -14,24 +14,10 @@ module GraphQL
|
|
14
14
|
# Just to encode data in the cursor, use something that won't conflict
|
15
15
|
CURSOR_SEPARATOR = "---"
|
16
16
|
|
17
|
-
# Map of collection
|
18
|
-
# eg Array
|
17
|
+
# Map of collection class names -> connection_classes
|
18
|
+
# eg {"Array" => ArrayConnection}
|
19
19
|
CONNECTION_IMPLEMENTATIONS = {}
|
20
20
|
|
21
|
-
# Create a connection which exposes edges of this type
|
22
|
-
def self.create_type(wrapped_type, &block)
|
23
|
-
edge_type = wrapped_type.edge_type
|
24
|
-
|
25
|
-
connection_type = ObjectType.define do
|
26
|
-
name("#{wrapped_type.name}Connection")
|
27
|
-
field :edges, types[edge_type]
|
28
|
-
field :pageInfo, PageInfo, property: :page_info
|
29
|
-
block && instance_eval(&block)
|
30
|
-
end
|
31
|
-
|
32
|
-
connection_type
|
33
|
-
end
|
34
|
-
|
35
21
|
# Find a connection implementation suitable for exposing `items`
|
36
22
|
#
|
37
23
|
# @param [Object] A collection of items (eg, Array, AR::Relation)
|
@@ -61,16 +47,18 @@ module GraphQL
|
|
61
47
|
CONNECTION_IMPLEMENTATIONS[items_class.name] = connection_class
|
62
48
|
end
|
63
49
|
|
64
|
-
attr_reader :object, :arguments, :max_page_size
|
50
|
+
attr_reader :object, :arguments, :max_page_size, :parent
|
65
51
|
|
66
52
|
# Make a connection, wrapping `object`
|
67
53
|
# @param The collection of results
|
68
54
|
# @param Query arguments
|
69
55
|
# @param max_page_size [Int] The maximum number of results to return
|
70
|
-
|
56
|
+
# @param parent [Object] The object which this collection belongs to
|
57
|
+
def initialize(object, arguments, max_page_size: nil, parent: nil)
|
71
58
|
@object = object
|
72
59
|
@arguments = arguments
|
73
60
|
@max_page_size = max_page_size
|
61
|
+
@parent = parent
|
74
62
|
end
|
75
63
|
|
76
64
|
# Provide easy access to provided arguments:
|
@@ -92,9 +80,10 @@ module GraphQL
|
|
92
80
|
end
|
93
81
|
end
|
94
82
|
|
95
|
-
#
|
96
|
-
|
97
|
-
|
83
|
+
# These are the items to render for this connection,
|
84
|
+
# probably wrapped by {GraphQL::Relay::Edge}
|
85
|
+
def edge_nodes
|
86
|
+
@edge_nodes ||= paged_nodes
|
98
87
|
end
|
99
88
|
|
100
89
|
# Support the `pageInfo` field
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
module ConnectionType
|
4
|
+
# Create a connection which exposes edges of this type
|
5
|
+
def self.create_type(wrapped_type, edge_type: nil, edge_class: nil, &block)
|
6
|
+
edge_type ||= wrapped_type.edge_type
|
7
|
+
edge_class ||= GraphQL::Relay::Edge
|
8
|
+
connection_type_name = "#{wrapped_type.name}Connection"
|
9
|
+
|
10
|
+
connection_type = ObjectType.define do
|
11
|
+
name(connection_type_name)
|
12
|
+
field :edges, types[edge_type] do
|
13
|
+
resolve -> (obj, args, ctx) {
|
14
|
+
obj.edge_nodes.map { |item| edge_class.new(item, obj) }
|
15
|
+
}
|
16
|
+
end
|
17
|
+
field :pageInfo, PageInfo, property: :page_info
|
18
|
+
block && instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
connection_type
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/graphql/relay/edge.rb
CHANGED
@@ -4,22 +4,19 @@ module GraphQL
|
|
4
4
|
#
|
5
5
|
# Wraps an object as a `node`, and exposes a connection-specific `cursor`.
|
6
6
|
class Edge < GraphQL::ObjectType
|
7
|
-
attr_reader :node
|
7
|
+
attr_reader :node, :parent, :connection
|
8
8
|
def initialize(node, connection)
|
9
9
|
@node = node
|
10
10
|
@connection = connection
|
11
|
+
@parent = parent
|
11
12
|
end
|
12
13
|
|
13
14
|
def cursor
|
14
|
-
@cursor ||=
|
15
|
+
@cursor ||= connection.cursor_from_node(node)
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
19
|
-
name("#{wrapped_type.name}Edge")
|
20
|
-
field :node, wrapped_type
|
21
|
-
field :cursor, !types.String
|
22
|
-
end
|
18
|
+
def parent
|
19
|
+
@parent ||= connection.parent
|
23
20
|
end
|
24
21
|
end
|
25
22
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
module EdgeType
|
4
|
+
def self.create_type(wrapped_type, name: nil, &block)
|
5
|
+
GraphQL::ObjectType.define do
|
6
|
+
name("#{wrapped_type.name}Edge")
|
7
|
+
field :node, wrapped_type
|
8
|
+
field :cursor, !types.String
|
9
|
+
block && instance_eval(&block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,16 +1,21 @@
|
|
1
1
|
class GraphQL::BaseType
|
2
|
+
# Get the default connection type for this object type
|
2
3
|
def connection_type
|
3
4
|
@connection_type ||= define_connection
|
4
5
|
end
|
5
6
|
|
7
|
+
# Define a custom connection type for this object type
|
8
|
+
def define_connection(**kwargs, &block)
|
9
|
+
GraphQL::Relay::ConnectionType.create_type(self, **kwargs, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get the default edge type for this object type
|
6
13
|
def edge_type
|
7
|
-
@edge_type ||=
|
14
|
+
@edge_type ||= define_edge
|
8
15
|
end
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
@connection_type = GraphQL::Relay::BaseConnection.create_type(self, &block)
|
17
|
+
# Define a custom edge type for this object type
|
18
|
+
def define_edge(**kwargs, &block)
|
19
|
+
GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block)
|
15
20
|
end
|
16
21
|
end
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
#
|
9
9
|
# `resolve` should return a hash with a key for each of the `return_field`s
|
10
10
|
#
|
11
|
-
# Inputs
|
11
|
+
# Inputs may also contain a `clientMutationId`
|
12
12
|
#
|
13
13
|
# @example Updating the name of an item
|
14
14
|
# UpdateNameMutation = GraphQL::Relay::Mutation.define do
|
@@ -94,7 +94,7 @@ module GraphQL
|
|
94
94
|
GraphQL::ObjectType.define do
|
95
95
|
name(type_name)
|
96
96
|
description("Autogenerated return type of #{mutation_name}")
|
97
|
-
field :clientMutationId,
|
97
|
+
field :clientMutationId, types.String
|
98
98
|
type_fields.each do |name, field_obj|
|
99
99
|
field name, field: field_obj
|
100
100
|
end
|
@@ -110,7 +110,7 @@ module GraphQL
|
|
110
110
|
GraphQL::InputObjectType.define do
|
111
111
|
name(type_name)
|
112
112
|
description("Autogenerated input type of #{mutation_name}")
|
113
|
-
input_field :clientMutationId,
|
113
|
+
input_field :clientMutationId, types.String
|
114
114
|
type_fields.each do |name, field_obj|
|
115
115
|
input_field name, field_obj.type, field_obj.description, default_value: field_obj.default_value
|
116
116
|
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module GraphQL
|
2
2
|
module Relay
|
3
|
+
# A connection implementation to expose SQL collection objects.
|
4
|
+
# It works for:
|
5
|
+
# - `ActiveRecord::Relation`
|
6
|
+
# - `Sequel::Dataset`
|
3
7
|
class RelationConnection < BaseConnection
|
4
8
|
def cursor_from_node(item)
|
5
9
|
offset = starting_offset + paged_nodes_array.index(item) + 1
|
@@ -93,5 +97,8 @@ module GraphQL
|
|
93
97
|
if defined?(ActiveRecord)
|
94
98
|
BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection)
|
95
99
|
end
|
100
|
+
if defined?(Sequel)
|
101
|
+
BaseConnection.register_connection_implementation(Sequel::Dataset, RelationConnection)
|
102
|
+
end
|
96
103
|
end
|
97
104
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Relay::ConnectionType do
|
4
|
+
describe ".create_type" do
|
5
|
+
describe "connections with custom Edge classes / EdgeTypes" do
|
6
|
+
let(:query_string) {%|
|
7
|
+
{
|
8
|
+
rebels {
|
9
|
+
basesWithCustomEdge {
|
10
|
+
totalCountTimes100
|
11
|
+
edges {
|
12
|
+
upcasedName
|
13
|
+
edgeClassName
|
14
|
+
node {
|
15
|
+
name
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|}
|
22
|
+
|
23
|
+
it "uses the custom edge and custom connection" do
|
24
|
+
result = query(query_string)
|
25
|
+
bases = result["data"]["rebels"]["basesWithCustomEdge"]
|
26
|
+
assert_equal 200, bases["totalCountTimes100"]
|
27
|
+
assert_equal ["YAVIN", "ECHO BASE"] , bases["edges"].map { |e| e["upcasedName"] }
|
28
|
+
assert_equal ["Yavin", "Echo Base"] , bases["edges"].map { |e| e["node"]["name"] }
|
29
|
+
assert_equal ["CustomBaseEdge", "CustomBaseEdge"] , bases["edges"].map { |e| e["edgeClassName"] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe GraphQL::Relay::Mutation do
|
4
4
|
let(:query_string) {%|
|
5
|
-
mutation addBagel {
|
6
|
-
introduceShip(input: {shipName: "Bagel", factionId: "1", clientMutationId:
|
5
|
+
mutation addBagel($clientMutationId: String) {
|
6
|
+
introduceShip(input: {shipName: "Bagel", factionId: "1", clientMutationId: $clientMutationId}) {
|
7
7
|
clientMutationId
|
8
8
|
ship { name, id }
|
9
9
|
faction { name }
|
@@ -18,8 +18,13 @@ describe GraphQL::Relay::Mutation do
|
|
18
18
|
}
|
19
19
|
|}
|
20
20
|
|
21
|
+
after do
|
22
|
+
STAR_WARS_DATA["Ship"].delete("9")
|
23
|
+
STAR_WARS_DATA["Faction"]["1"]["ships"].delete("9")
|
24
|
+
end
|
25
|
+
|
21
26
|
it "returns the result & clientMutationId" do
|
22
|
-
result = query(query_string)
|
27
|
+
result = query(query_string, "clientMutationId" => "1234")
|
23
28
|
expected = {"data" => {
|
24
29
|
"introduceShip" => {
|
25
30
|
"clientMutationId" => "1234",
|
@@ -31,8 +36,20 @@ describe GraphQL::Relay::Mutation do
|
|
31
36
|
}
|
32
37
|
}}
|
33
38
|
assert_equal(expected, result)
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
end
|
40
|
+
|
41
|
+
it "doesn't require a clientMutationId to perform mutations" do
|
42
|
+
result = query(query_string)
|
43
|
+
expected = {"data" => {
|
44
|
+
"introduceShip" => {
|
45
|
+
"clientMutationId" => nil,
|
46
|
+
"ship" => {
|
47
|
+
"name" => "Bagel",
|
48
|
+
"id" => NodeIdentification.to_global_id("Ship", "9"),
|
49
|
+
},
|
50
|
+
"faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
|
51
|
+
}
|
52
|
+
}}
|
53
|
+
assert_equal(expected, result)
|
37
54
|
end
|
38
55
|
end
|
@@ -20,7 +20,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
20
20
|
}
|
21
21
|
}
|
22
22
|
|
23
|
-
fragment basesConnection on
|
23
|
+
fragment basesConnection on BasesConnectionWithTotalCount {
|
24
24
|
totalCount,
|
25
25
|
edges {
|
26
26
|
cursor
|
@@ -160,7 +160,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
160
160
|
query getBases {
|
161
161
|
empire {
|
162
162
|
basesByName(first: 30) { ... basesFields }
|
163
|
-
bases(first: 30) { ...
|
163
|
+
bases(first: 30) { ... basesFields2 }
|
164
164
|
}
|
165
165
|
}
|
166
166
|
fragment basesFields on BaseConnection {
|
@@ -170,6 +170,13 @@ describe GraphQL::Relay::RelationConnection do
|
|
170
170
|
}
|
171
171
|
}
|
172
172
|
}
|
173
|
+
fragment basesFields2 on BasesConnectionWithTotalCount {
|
174
|
+
edges {
|
175
|
+
node {
|
176
|
+
name
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
173
180
|
|}
|
174
181
|
|
175
182
|
def get_names(result, field_name)
|
@@ -179,7 +186,6 @@ describe GraphQL::Relay::RelationConnection do
|
|
179
186
|
|
180
187
|
it "applies the default value" do
|
181
188
|
result = query(query_string)
|
182
|
-
|
183
189
|
bases_by_id = ["Death Star", "Shield Generator", "Headquarters"]
|
184
190
|
bases_by_name = ["Death Star", "Headquarters", "Shield Generator"]
|
185
191
|
|
@@ -187,4 +193,93 @@ describe GraphQL::Relay::RelationConnection do
|
|
187
193
|
assert_equal(bases_by_name, get_names(result, "basesByName"))
|
188
194
|
end
|
189
195
|
end
|
196
|
+
|
197
|
+
describe "with a Sequel::Dataset" do
|
198
|
+
def get_names(result)
|
199
|
+
ships = result["data"]["empire"]["basesAsSequelDataset"]["edges"]
|
200
|
+
names = ships.map { |e| e["node"]["name"] }
|
201
|
+
end
|
202
|
+
|
203
|
+
def get_last_cursor(result)
|
204
|
+
result["data"]["empire"]["basesAsSequelDataset"]["edges"].last["cursor"]
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "results" do
|
208
|
+
let(:query_string) {%|
|
209
|
+
query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
|
210
|
+
empire {
|
211
|
+
basesAsSequelDataset(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
|
212
|
+
... basesConnection
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
fragment basesConnection on BasesConnectionWithTotalCount {
|
218
|
+
totalCount,
|
219
|
+
edges {
|
220
|
+
cursor
|
221
|
+
node {
|
222
|
+
name
|
223
|
+
}
|
224
|
+
},
|
225
|
+
pageInfo {
|
226
|
+
hasNextPage
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|}
|
230
|
+
|
231
|
+
it 'limits the result' do
|
232
|
+
result = query(query_string, "first" => 2)
|
233
|
+
assert_equal(2, get_names(result).length)
|
234
|
+
|
235
|
+
result = query(query_string, "first" => 3)
|
236
|
+
assert_equal(3, get_names(result).length)
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'provides custom fileds on the connection type' do
|
240
|
+
result = query(query_string, "first" => 2)
|
241
|
+
assert_equal(
|
242
|
+
Base.where(faction_id: 2).count,
|
243
|
+
result["data"]["empire"]["basesAsSequelDataset"]["totalCount"]
|
244
|
+
)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'slices the result' do
|
248
|
+
result = query(query_string, "first" => 2)
|
249
|
+
assert_equal(["Death Star", "Shield Generator"], get_names(result))
|
250
|
+
|
251
|
+
# After the last result, find the next 2:
|
252
|
+
last_cursor = get_last_cursor(result)
|
253
|
+
|
254
|
+
result = query(query_string, "after" => last_cursor, "first" => 2)
|
255
|
+
assert_equal(["Headquarters"], get_names(result))
|
256
|
+
|
257
|
+
last_cursor = get_last_cursor(result)
|
258
|
+
|
259
|
+
result = query(query_string, "before" => last_cursor, "last" => 1)
|
260
|
+
assert_equal(["Shield Generator"], get_names(result))
|
261
|
+
|
262
|
+
result = query(query_string, "before" => last_cursor, "last" => 2)
|
263
|
+
assert_equal(["Death Star", "Shield Generator"], get_names(result))
|
264
|
+
|
265
|
+
result = query(query_string, "before" => last_cursor, "last" => 10)
|
266
|
+
assert_equal(["Death Star", "Shield Generator"], get_names(result))
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
it "applies custom arguments" do
|
271
|
+
result = query(query_string, "first" => 1, "nameIncludes" => "ea")
|
272
|
+
assert_equal(["Death Star"], get_names(result))
|
273
|
+
|
274
|
+
after = get_last_cursor(result)
|
275
|
+
|
276
|
+
result = query(query_string, "first" => 2, "nameIncludes" => "ea", "after" => after )
|
277
|
+
assert_equal(["Headquarters"], get_names(result))
|
278
|
+
before = get_last_cursor(result)
|
279
|
+
|
280
|
+
result = query(query_string, "last" => 1, "nameIncludes" => "ea", "before" => before)
|
281
|
+
assert_equal(["Death Star"], get_names(result))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
190
285
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,6 +2,7 @@ require "codeclimate-test-reporter"
|
|
2
2
|
CodeClimate::TestReporter.start
|
3
3
|
require "sqlite3"
|
4
4
|
require "active_record"
|
5
|
+
require "sequel"
|
5
6
|
require "graphql/relay"
|
6
7
|
require "minitest/autorun"
|
7
8
|
require "minitest/focus"
|
@@ -16,5 +17,5 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
|
16
17
|
|
17
18
|
|
18
19
|
def query(string, variables={})
|
19
|
-
GraphQL::Query.new(StarWarsSchema, string, variables: variables
|
20
|
+
GraphQL::Query.new(StarWarsSchema, string, variables: variables).result
|
20
21
|
end
|
@@ -10,9 +10,10 @@ names = [
|
|
10
10
|
'Executor',
|
11
11
|
]
|
12
12
|
|
13
|
-
## Set up "Bases" in ActiveRecord
|
14
13
|
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
15
|
-
|
14
|
+
`rm -f ./_test_.db`
|
15
|
+
# Set up "Bases" in ActiveRecord
|
16
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "./_test_.db")
|
16
17
|
|
17
18
|
ActiveRecord::Schema.define do
|
18
19
|
self.verbose = false
|
@@ -32,6 +33,11 @@ Base.create!(name: "Death Star", planet: nil, faction_id: 2)
|
|
32
33
|
Base.create!(name: "Shield Generator", planet: "Endor", faction_id: 2)
|
33
34
|
Base.create!(name: "Headquarters", planet: "Coruscant", faction_id: 2)
|
34
35
|
|
36
|
+
# Also, set up Bases with Sequel
|
37
|
+
DB = Sequel.sqlite("./_test_.db")
|
38
|
+
class SequelBase < Sequel::Model(:bases)
|
39
|
+
end
|
40
|
+
|
35
41
|
rebels = OpenStruct.new({
|
36
42
|
id: '1',
|
37
43
|
name: 'Alliance to Restore the Republic',
|
@@ -47,15 +47,38 @@ BaseType = GraphQL::ObjectType.define do
|
|
47
47
|
field :planet, types.String
|
48
48
|
end
|
49
49
|
|
50
|
-
#
|
51
|
-
|
52
|
-
|
50
|
+
# Use an optional block to add fields to the connection type:
|
51
|
+
BaseConnectionWithTotalCountType = BaseType.define_connection do
|
52
|
+
name "BasesConnectionWithTotalCount"
|
53
53
|
field :totalCount do
|
54
54
|
type types.Int
|
55
55
|
resolve -> (obj, args, ctx) { obj.object.count }
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
class CustomBaseEdge < GraphQL::Relay::Edge
|
60
|
+
def upcased_name
|
61
|
+
node.name.upcase
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
CustomBaseEdgeType = BaseType.define_edge do
|
66
|
+
name "CustomBaseEdge"
|
67
|
+
field :upcasedName, types.String, property: :upcased_name
|
68
|
+
field :edgeClassName, types.String do
|
69
|
+
resolve -> (obj, args, ctx) { obj.class.name }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
CustomEdgeBaseConnectionType = BaseType.define_connection(edge_class: CustomBaseEdge, edge_type: CustomBaseEdgeType) do
|
74
|
+
name "CustomEdgeBaseConnection"
|
75
|
+
|
76
|
+
field :totalCountTimes100 do
|
77
|
+
type types.Int
|
78
|
+
resolve -> (obj, args, ctx) { obj.object.count * 100 }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
59
82
|
|
60
83
|
Faction = GraphQL::ObjectType.define do
|
61
84
|
name "Faction"
|
@@ -75,7 +98,7 @@ Faction = GraphQL::ObjectType.define do
|
|
75
98
|
# You can define arguments here and use them in the connection
|
76
99
|
argument :nameIncludes, types.String
|
77
100
|
end
|
78
|
-
connection :bases,
|
101
|
+
connection :bases, BaseConnectionWithTotalCountType do
|
79
102
|
# Resolve field should return an Array, the Connection
|
80
103
|
# will do the rest!
|
81
104
|
resolve -> (obj, args, ctx) {
|
@@ -107,6 +130,19 @@ Faction = GraphQL::ObjectType.define do
|
|
107
130
|
connection :basesWithMaxLimitArray, BaseType.connection_type, max_page_size: 2 do
|
108
131
|
resolve -> (object, args, context) { Base.all.to_a }
|
109
132
|
end
|
133
|
+
|
134
|
+
connection :basesAsSequelDataset, BaseConnectionWithTotalCountType do
|
135
|
+
argument :nameIncludes, types.String
|
136
|
+
resolve -> (obj, args, ctx) {
|
137
|
+
all_bases = SequelBase.where(faction_id: obj.id)
|
138
|
+
if args[:nameIncludes]
|
139
|
+
all_bases = all_bases.where("name LIKE ?", "%#{args[:nameIncludes]}%")
|
140
|
+
end
|
141
|
+
all_bases
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
connection :basesWithCustomEdge, CustomEdgeBaseConnectionType, property: :bases
|
110
146
|
end
|
111
147
|
|
112
148
|
# Define a mutation. It will also:
|
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.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -178,6 +178,20 @@ dependencies:
|
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '11.0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: sequel
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
181
195
|
- !ruby/object:Gem::Dependency
|
182
196
|
name: sqlite3
|
183
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,8 +220,10 @@ files:
|
|
206
220
|
- lib/graphql/relay/array_connection.rb
|
207
221
|
- lib/graphql/relay/base_connection.rb
|
208
222
|
- lib/graphql/relay/connection_field.rb
|
223
|
+
- lib/graphql/relay/connection_type.rb
|
209
224
|
- lib/graphql/relay/define.rb
|
210
225
|
- lib/graphql/relay/edge.rb
|
226
|
+
- lib/graphql/relay/edge_type.rb
|
211
227
|
- lib/graphql/relay/global_id_field.rb
|
212
228
|
- lib/graphql/relay/global_node_identification.rb
|
213
229
|
- lib/graphql/relay/monkey_patches/base_type.rb
|
@@ -216,6 +232,7 @@ files:
|
|
216
232
|
- lib/graphql/relay/relation_connection.rb
|
217
233
|
- lib/graphql/relay/version.rb
|
218
234
|
- spec/graphql/relay/array_connection_spec.rb
|
235
|
+
- spec/graphql/relay/connection_type_spec.rb
|
219
236
|
- spec/graphql/relay/global_node_identification_spec.rb
|
220
237
|
- spec/graphql/relay/mutation_spec.rb
|
221
238
|
- spec/graphql/relay/page_info_spec.rb
|
@@ -249,6 +266,7 @@ specification_version: 4
|
|
249
266
|
summary: Relay helpers for GraphQL
|
250
267
|
test_files:
|
251
268
|
- spec/graphql/relay/array_connection_spec.rb
|
269
|
+
- spec/graphql/relay/connection_type_spec.rb
|
252
270
|
- spec/graphql/relay/global_node_identification_spec.rb
|
253
271
|
- spec/graphql/relay/mutation_spec.rb
|
254
272
|
- spec/graphql/relay/page_info_spec.rb
|