graphql-relay 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby)
|
6
6
|
[![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/badges/coverage.svg)](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
|