graphql-relay 0.4.2 → 0.4.3
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 +3 -0
- data/lib/graphql/relay/base_connection.rb +20 -5
- data/lib/graphql/relay/connection_field.rb +12 -1
- data/lib/graphql/relay/monkey_patches/definition_config.rb +0 -3
- data/lib/graphql/relay/relation_connection.rb +3 -2
- data/lib/graphql/relay/version.rb +1 -1
- data/spec/graphql/relay/global_node_identification_spec.rb +27 -5
- data/spec/graphql/relay/page_info_spec.rb +65 -0
- data/spec/graphql/relay/relation_connection_spec.rb +6 -17
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1038ccd66be579e1ade39b942de12fd993cbdc26
|
4
|
+
data.tar.gz: 278b0a2e1dbbe0ddb15669e6fcfb0e4206bbab0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8bf419f8e9c9c55d93a2c1efe15e3e66e81772ef04fe537046205d5d42f1c84bda9c690b3075625f6d80947d2b6fcce9df7032d98507219a70426af26db85ed
|
7
|
+
data.tar.gz: ee7c7e669c03577c0924838f6a985b0639ee9008a5e0fbdfaa24eec75f3913e713b41eeb362802cf42eb2f32b2fd1ac851e74efdb6a2da9c59f2d5ea9ad97dd7
|
data/README.md
CHANGED
@@ -44,6 +44,8 @@ Examples:
|
|
44
44
|
|
45
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)
|
46
46
|
|
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
|
+
|
47
49
|
### Mutations
|
48
50
|
|
49
51
|
Mutations allow Relay to mutate your system. When you define a mutation, you'll be defining:
|
@@ -71,6 +73,7 @@ Examples:
|
|
71
73
|
## Todo
|
72
74
|
|
73
75
|
- Show how to replace default connection implementations with custom ones
|
76
|
+
- Show how to wrap a simple field in a connection field with `ConnectionField.create`
|
74
77
|
|
75
78
|
## More Resources
|
76
79
|
|
@@ -31,7 +31,10 @@ module GraphQL
|
|
31
31
|
connection_type
|
32
32
|
end
|
33
33
|
|
34
|
-
#
|
34
|
+
# Find a connection implementation suitable for exposing `items`
|
35
|
+
#
|
36
|
+
# @param [Object] A collection of items (eg, Array, AR::Relation)
|
37
|
+
# @return [subclass of BaseConnection] a connection Class for wrapping `items`
|
35
38
|
def self.connection_for_items(items)
|
36
39
|
implementation = CONNECTION_IMPLEMENTATIONS.find do |items_class, connection_class|
|
37
40
|
items.is_a?(items_class)
|
@@ -45,6 +48,8 @@ module GraphQL
|
|
45
48
|
|
46
49
|
# Add `connection_class` as the connection wrapper for `items_class`
|
47
50
|
# eg, `RelationConnection` is the implementation for `AR::Relation`
|
51
|
+
# @param [Class] A class representing a collection (eg, Array, AR::Relation)
|
52
|
+
# @param [Class] A class implementing Connection methods
|
48
53
|
def self.register_connection_implementation(items_class, connection_class)
|
49
54
|
CONNECTION_IMPLEMENTATIONS[items_class] = connection_class
|
50
55
|
end
|
@@ -59,6 +64,16 @@ module GraphQL
|
|
59
64
|
# Provide easy access to provided arguments:
|
60
65
|
METHODS_FROM_ARGUMENTS = [:first, :after, :last, :before, :order]
|
61
66
|
|
67
|
+
# @!method first
|
68
|
+
# The value passed as `first:`, if there was one
|
69
|
+
# @!method after
|
70
|
+
# The value passed as `after:`, if there was one
|
71
|
+
# @!method last
|
72
|
+
# The value passed as `last:`, if there was one
|
73
|
+
# @!method before
|
74
|
+
# The value passed as `before:`, if there was one
|
75
|
+
# @!method order
|
76
|
+
# The value passed as `order:`, if there was one
|
62
77
|
METHODS_FROM_ARGUMENTS.each do |arg_name|
|
63
78
|
define_method(arg_name) do
|
64
79
|
arguments[arg_name]
|
@@ -77,12 +92,12 @@ module GraphQL
|
|
77
92
|
|
78
93
|
# Used by `pageInfo`
|
79
94
|
def has_next_page
|
80
|
-
first && sliced_nodes.count > first
|
95
|
+
!!(first && sliced_nodes.count > first)
|
81
96
|
end
|
82
97
|
|
83
98
|
# Used by `pageInfo`
|
84
99
|
def has_previous_page
|
85
|
-
last && sliced_nodes.count > last
|
100
|
+
!!(last && sliced_nodes.count > last)
|
86
101
|
end
|
87
102
|
|
88
103
|
# An opaque operation which returns a connection-specific cursor.
|
@@ -93,11 +108,11 @@ module GraphQL
|
|
93
108
|
private
|
94
109
|
|
95
110
|
def paged_nodes
|
96
|
-
raise NotImplementedError, "must items for this connection after paging"
|
111
|
+
raise NotImplementedError, "must return items for this connection after paging"
|
97
112
|
end
|
98
113
|
|
99
114
|
def sliced_nodes
|
100
|
-
raise NotImplementedError, "must all items for this connection after chopping off first and last"
|
115
|
+
raise NotImplementedError, "must return all items for this connection after chopping off first and last"
|
101
116
|
end
|
102
117
|
end
|
103
118
|
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module GraphQL
|
2
2
|
module Relay
|
3
|
+
# Provided a GraphQL field which returns a collection of items,
|
4
|
+
# `ConnectionField.create` modifies that field to expose those items
|
5
|
+
# as a collection.
|
6
|
+
#
|
7
|
+
# The original resolve proc is used to fetch items,
|
8
|
+
# then a connection implementation is fetched with {BaseConnection.connection_for_items}.
|
3
9
|
class ConnectionField
|
4
10
|
ARGUMENT_DEFINITIONS = [
|
5
11
|
[:first, GraphQL::INT_TYPE],
|
@@ -18,8 +24,10 @@ module GraphQL
|
|
18
24
|
end
|
19
25
|
|
20
26
|
# Turn A GraphQL::Field into a connection by:
|
21
|
-
# - Merging in the default
|
27
|
+
# - Merging in the default arguments
|
22
28
|
# - Transforming its resolve function to return a connection object
|
29
|
+
# @param [GraphQL::Field] A field which returns items to be wrapped as a connection
|
30
|
+
# @return [GraphQL::Field] A field which serves a connections
|
23
31
|
def self.create(underlying_field)
|
24
32
|
underlying_field.arguments = underlying_field.arguments.merge(DEFAULT_ARGUMENTS)
|
25
33
|
# TODO: make a public API on GraphQL::Field to expose this proc
|
@@ -30,6 +38,9 @@ module GraphQL
|
|
30
38
|
|
31
39
|
private
|
32
40
|
|
41
|
+
# Wrap the original resolve proc
|
42
|
+
# so you capture its value, then wrap it in a
|
43
|
+
# connection implementation
|
33
44
|
def self.get_connection_resolve(field_name, underlying_resolve)
|
34
45
|
-> (obj, args, ctx) {
|
35
46
|
items = underlying_resolve.call(obj, args, ctx)
|
@@ -1,8 +1,5 @@
|
|
1
1
|
class GraphQL::DefinitionHelpers::DefinedByConfig::DefinitionConfig
|
2
2
|
# Wraps a field definition with a ConnectionField
|
3
|
-
# - applies default fields
|
4
|
-
# - wraps the resolve proc to make a connection
|
5
|
-
#
|
6
3
|
def connection(name, type = nil, desc = nil, property: nil, &block)
|
7
4
|
underlying_field = field(name, type, desc, property: property, &block)
|
8
5
|
connection_field = GraphQL::Relay::ConnectionField.create(underlying_field)
|
@@ -11,7 +11,6 @@ module GraphQL
|
|
11
11
|
@order ||= (super || "id")
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
14
|
private
|
16
15
|
|
17
16
|
# apply first / last limit results
|
@@ -19,7 +18,7 @@ module GraphQL
|
|
19
18
|
@paged_nodes = begin
|
20
19
|
items = sliced_nodes
|
21
20
|
first && items = items.first(first)
|
22
|
-
last && items.
|
21
|
+
last && items.count > last && items = items.last(last)
|
23
22
|
items
|
24
23
|
end
|
25
24
|
end
|
@@ -71,6 +70,8 @@ module GraphQL
|
|
71
70
|
["#{name} #{direction_marker} ?", value]
|
72
71
|
end
|
73
72
|
end
|
73
|
+
|
74
|
+
|
74
75
|
if defined?(ActiveRecord)
|
75
76
|
BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection)
|
76
77
|
end
|
@@ -4,12 +4,34 @@ describe GraphQL::Relay::GlobalNodeIdentification do
|
|
4
4
|
let(:node_identification) { NodeIdentification }
|
5
5
|
describe 'NodeField' do
|
6
6
|
it 'finds objects by id' do
|
7
|
-
global_id = node_identification.to_global_id("
|
8
|
-
result = query(%|{
|
7
|
+
global_id = node_identification.to_global_id("Faction", "1")
|
8
|
+
result = query(%|{
|
9
|
+
node(id: "#{global_id}") {
|
10
|
+
id,
|
11
|
+
... on Faction {
|
12
|
+
name
|
13
|
+
ships(first: 1) {
|
14
|
+
edges {
|
15
|
+
node {
|
16
|
+
name
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}|)
|
9
23
|
expected = {"data" => {
|
10
|
-
"node"
|
11
|
-
"id"
|
12
|
-
"name"
|
24
|
+
"node"=>{
|
25
|
+
"id"=>"RmFjdGlvbi0x",
|
26
|
+
"name"=>"Alliance to Restore the Republic",
|
27
|
+
"ships"=>{
|
28
|
+
"edges"=>[
|
29
|
+
{"node"=>{
|
30
|
+
"name" => "X-Wing"
|
31
|
+
}
|
32
|
+
}
|
33
|
+
]
|
34
|
+
}
|
13
35
|
}
|
14
36
|
}}
|
15
37
|
assert_equal(expected, result)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Relay::PageInfo do
|
4
|
+
def get_page_info(result)
|
5
|
+
result["data"]["empire"]["bases"]["pageInfo"]
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_last_cursor(result)
|
9
|
+
result["data"]["empire"]["bases"]["edges"].last["cursor"]
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:cursor_of_last_base) {
|
13
|
+
result = query(query_string, "first" => 3)
|
14
|
+
last_cursor = get_last_cursor(result)
|
15
|
+
}
|
16
|
+
|
17
|
+
let(:query_string) {%|
|
18
|
+
query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String, $nameIncludes: String){
|
19
|
+
empire {
|
20
|
+
bases(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
|
21
|
+
edges {
|
22
|
+
cursor
|
23
|
+
}
|
24
|
+
pageInfo {
|
25
|
+
hasNextPage
|
26
|
+
hasPreviousPage
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|}
|
32
|
+
|
33
|
+
describe 'hasNextPage / hasPreviousPage' do
|
34
|
+
it "hasNextPage is true if there are more items" do
|
35
|
+
result = query(query_string, "first" => 2)
|
36
|
+
assert_equal(true, get_page_info(result)["hasNextPage"])
|
37
|
+
assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false if 'last' is missing")
|
38
|
+
|
39
|
+
last_cursor = get_last_cursor(result)
|
40
|
+
result = query(query_string, "first" => 100, "after" => last_cursor)
|
41
|
+
assert_equal(false, get_page_info(result)["hasNextPage"])
|
42
|
+
assert_equal(false, get_page_info(result)["hasPreviousPage"])
|
43
|
+
end
|
44
|
+
|
45
|
+
it "hasPreviousPage if there are more items" do
|
46
|
+
result = query(query_string, "last" => 100, "before" => cursor_of_last_base)
|
47
|
+
assert_equal(false, get_page_info(result)["hasNextPage"])
|
48
|
+
assert_equal(false, get_page_info(result)["hasPreviousPage"])
|
49
|
+
|
50
|
+
result = query(query_string, "last" => 1, "before" => cursor_of_last_base)
|
51
|
+
assert_equal(false, get_page_info(result)["hasNextPage"])
|
52
|
+
assert_equal(true, get_page_info(result)["hasPreviousPage"])
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
it "has both if first and last are present" do
|
57
|
+
result = query(query_string, "last" => 1, "first" => 1, "before" => cursor_of_last_base)
|
58
|
+
assert_equal(true, get_page_info(result)["hasNextPage"])
|
59
|
+
assert_equal(true, get_page_info(result)["hasPreviousPage"])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
end
|
@@ -6,10 +6,6 @@ describe GraphQL::Relay::RelationConnection do
|
|
6
6
|
names = ships.map { |e| e["node"]["name"] }
|
7
7
|
end
|
8
8
|
|
9
|
-
def get_page_info(result)
|
10
|
-
result["data"]["empire"]["bases"]["pageInfo"]
|
11
|
-
end
|
12
|
-
|
13
9
|
def get_last_cursor(result)
|
14
10
|
result["data"]["empire"]["bases"]["edges"].last["cursor"]
|
15
11
|
end
|
@@ -20,9 +16,6 @@ describe GraphQL::Relay::RelationConnection do
|
|
20
16
|
empire {
|
21
17
|
bases(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
|
22
18
|
... basesConnection
|
23
|
-
pageInfo {
|
24
|
-
hasNextPage
|
25
|
-
}
|
26
19
|
}
|
27
20
|
}
|
28
21
|
}
|
@@ -46,14 +39,6 @@ describe GraphQL::Relay::RelationConnection do
|
|
46
39
|
assert_equal(3, get_names(result).length)
|
47
40
|
end
|
48
41
|
|
49
|
-
it 'provides pageInfo' do
|
50
|
-
result = query(query_string, "first" => 2)
|
51
|
-
assert_equal(true, get_page_info(result)["hasNextPage"])
|
52
|
-
|
53
|
-
result = query(query_string, "first" => 100)
|
54
|
-
assert_equal(false, get_page_info(result)["hasNextPage"])
|
55
|
-
end
|
56
|
-
|
57
42
|
it 'provides custom fileds on the connection type' do
|
58
43
|
result = query(query_string, "first" => 2)
|
59
44
|
assert_equal(
|
@@ -72,8 +57,12 @@ describe GraphQL::Relay::RelationConnection do
|
|
72
57
|
result = query(query_string, "after" => last_cursor, "first" => 2)
|
73
58
|
assert_equal(["Headquarters"], get_names(result))
|
74
59
|
|
60
|
+
last_cursor = get_last_cursor(result)
|
61
|
+
result = query(query_string, "before" => last_cursor, "last" => 1)
|
62
|
+
assert_equal(["Shield Generator"], get_names(result))
|
63
|
+
|
75
64
|
result = query(query_string, "before" => last_cursor, "last" => 2)
|
76
|
-
assert_equal(["Death Star"], get_names(result))
|
65
|
+
assert_equal(["Death Star", "Shield Generator"], get_names(result))
|
77
66
|
end
|
78
67
|
|
79
68
|
it 'paginates with order' do
|
@@ -97,7 +86,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
97
86
|
assert_equal(["Death Star", "Headquarters"], get_names(result))
|
98
87
|
|
99
88
|
# After the last result, find the next 2:
|
100
|
-
last_cursor = result
|
89
|
+
last_cursor = get_last_cursor(result)
|
101
90
|
|
102
91
|
result = query(query_string, "after" => last_cursor, "first" => 2, "order" => "name")
|
103
92
|
assert_equal(["Shield Generator"], get_names(result))
|
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.
|
4
|
+
version: 0.4.3
|
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-09-
|
11
|
+
date: 2015-09-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -203,6 +203,7 @@ files:
|
|
203
203
|
- spec/graphql/relay/array_connection_spec.rb
|
204
204
|
- spec/graphql/relay/global_node_identification_spec.rb
|
205
205
|
- spec/graphql/relay/mutation_spec.rb
|
206
|
+
- spec/graphql/relay/page_info_spec.rb
|
206
207
|
- spec/graphql/relay/relation_connection_spec.rb
|
207
208
|
- spec/spec_helper.rb
|
208
209
|
- spec/support/star_wars_data.rb
|
@@ -235,6 +236,7 @@ test_files:
|
|
235
236
|
- spec/graphql/relay/array_connection_spec.rb
|
236
237
|
- spec/graphql/relay/global_node_identification_spec.rb
|
237
238
|
- spec/graphql/relay/mutation_spec.rb
|
239
|
+
- spec/graphql/relay/page_info_spec.rb
|
238
240
|
- spec/graphql/relay/relation_connection_spec.rb
|
239
241
|
- spec/spec_helper.rb
|
240
242
|
- spec/support/star_wars_data.rb
|