graphql-relay 0.8.1 → 0.9.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/MIT-LICENSE +20 -0
- data/README.md +1 -3
- data/lib/graphql/relay/array_connection.rb +36 -22
- data/lib/graphql/relay/connection_field.rb +1 -3
- data/lib/graphql/relay/relation_connection.rb +38 -58
- data/lib/graphql/relay/version.rb +1 -1
- data/spec/graphql/relay/array_connection_spec.rb +11 -21
- data/spec/graphql/relay/page_info_spec.rb +2 -2
- data/spec/graphql/relay/relation_connection_spec.rb +9 -20
- data/spec/support/star_wars_schema.rb +7 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fb50f9e3030be285c1be81b0f0e302919a49c2f
|
4
|
+
data.tar.gz: fd869d0de9f2f47085c12e94c093a0ca791ee004
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68734a2849039b20d4fcfe57855217247aebbf47c3252a22b96993e26880f0f4b739f41c75c203ce32fcf4e5e15d7b0c0a2b3f2f447ae838a21f6abfaf6617f2
|
7
|
+
data.tar.gz: c76dd82ddc3eef84a26fd1ab949b9e4483d9d2c81ea6c41e1c08497fc900c0b476b55b2d157b8091aab1906f438317822c1f4718f56b1eec00f11d54e62f8e5f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Robert Mosolgo
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -198,8 +198,6 @@ First, define the custom connection:
|
|
198
198
|
```ruby
|
199
199
|
class SetConnection < BaseConnection
|
200
200
|
# derive a cursor from `item`
|
201
|
-
# (it is used to find next & previous nodes,
|
202
|
-
# so it should include `order`)
|
203
201
|
def cursor_from_node(item)
|
204
202
|
# ...
|
205
203
|
end
|
@@ -344,10 +342,10 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
|
|
344
342
|
|
345
343
|
## Todo
|
346
344
|
|
347
|
-
- Allow custom defined ID scheme
|
348
345
|
- Allow custom edge fields (per connection type)
|
349
346
|
- `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?)
|
350
347
|
- Make GlobalId a property of the schema, not a global
|
348
|
+
- Reduce duplication in ArrayConnection / RelationConnection
|
351
349
|
|
352
350
|
## More Resources
|
353
351
|
|
@@ -1,13 +1,9 @@
|
|
1
1
|
module GraphQL
|
2
2
|
module Relay
|
3
3
|
class ArrayConnection < BaseConnection
|
4
|
-
# Just to encode data in the cursor, use something that won't conflict
|
5
|
-
CURSOR_SEPARATOR = "---"
|
6
|
-
|
7
4
|
def cursor_from_node(item)
|
8
|
-
idx = sliced_nodes.find_index(item)
|
9
|
-
|
10
|
-
Base64.strict_encode64(cursor_parts.join(CURSOR_SEPARATOR))
|
5
|
+
idx = starting_offset + sliced_nodes.find_index(item) + 1
|
6
|
+
Base64.strict_encode64(idx.to_s)
|
11
7
|
end
|
12
8
|
|
13
9
|
private
|
@@ -16,10 +12,7 @@ module GraphQL
|
|
16
12
|
def paged_nodes
|
17
13
|
@paged_nodes = begin
|
18
14
|
items = sliced_nodes
|
19
|
-
limit
|
20
|
-
first && items = items.first(limit)
|
21
|
-
last && items.length > last && items.last(limit)
|
22
|
-
items
|
15
|
+
items.first(limit)
|
23
16
|
end
|
24
17
|
end
|
25
18
|
|
@@ -27,22 +20,43 @@ module GraphQL
|
|
27
20
|
def sliced_nodes
|
28
21
|
@sliced_nodes ||= begin
|
29
22
|
items = object
|
30
|
-
|
31
|
-
# Remove possible direction marker:
|
32
|
-
order_name = order.sub(/^-/, '')
|
33
|
-
items = items.sort_by { |item| item.public_send(order_name) }
|
34
|
-
order.start_with?("-") && items = items.reverse
|
35
|
-
end
|
36
|
-
after && items = items[(1 + index_from_cursor(after))..-1]
|
37
|
-
before && items = items[0..(index_from_cursor(before) - 1)]
|
38
|
-
items
|
23
|
+
items[starting_offset..-1]
|
39
24
|
end
|
40
25
|
end
|
41
26
|
|
42
27
|
def index_from_cursor(cursor)
|
43
|
-
|
44
|
-
|
45
|
-
|
28
|
+
Base64.decode64(cursor).to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def starting_offset
|
32
|
+
@starting_offset = if before
|
33
|
+
[previous_offset, 0].max
|
34
|
+
else
|
35
|
+
previous_offset
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def previous_offset
|
40
|
+
@initial_offset ||= if before
|
41
|
+
index_from_cursor(before) - last - 1
|
42
|
+
elsif after
|
43
|
+
index_from_cursor(after)
|
44
|
+
else
|
45
|
+
0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def limit
|
50
|
+
@limit ||= if first
|
51
|
+
[first, max_page_size].compact.min
|
52
|
+
else
|
53
|
+
last_limit = if previous_offset <= 0
|
54
|
+
previous_offset + last
|
55
|
+
else
|
56
|
+
last
|
57
|
+
end
|
58
|
+
[last_limit, max_page_size].compact.min
|
59
|
+
end
|
46
60
|
end
|
47
61
|
end
|
48
62
|
BaseConnection.register_connection_implementation(Array, ArrayConnection)
|
@@ -12,7 +12,6 @@ module GraphQL
|
|
12
12
|
[:after, GraphQL::STRING_TYPE],
|
13
13
|
[:last, GraphQL::INT_TYPE],
|
14
14
|
[:before, GraphQL::STRING_TYPE],
|
15
|
-
[:order, GraphQL::STRING_TYPE],
|
16
15
|
]
|
17
16
|
|
18
17
|
DEFAULT_ARGUMENTS = ARGUMENT_DEFINITIONS.reduce({}) do |memo, arg_defn|
|
@@ -31,8 +30,7 @@ module GraphQL
|
|
31
30
|
# @return [GraphQL::Field] A field which serves a connections
|
32
31
|
def self.create(underlying_field, max_page_size: nil)
|
33
32
|
underlying_field.arguments = DEFAULT_ARGUMENTS.merge(underlying_field.arguments)
|
34
|
-
|
35
|
-
original_resolve = underlying_field.instance_variable_get(:@resolve_proc)
|
33
|
+
original_resolve = underlying_field.resolve_proc
|
36
34
|
underlying_field.resolve = get_connection_resolve(underlying_field.name, original_resolve, max_page_size: max_page_size)
|
37
35
|
underlying_field
|
38
36
|
end
|
@@ -1,16 +1,9 @@
|
|
1
1
|
module GraphQL
|
2
2
|
module Relay
|
3
3
|
class RelationConnection < BaseConnection
|
4
|
-
DEFAULT_ORDER = "id"
|
5
|
-
|
6
4
|
def cursor_from_node(item)
|
7
|
-
|
8
|
-
|
9
|
-
Base64.strict_encode64(cursor_parts.join(CURSOR_SEPARATOR))
|
10
|
-
end
|
11
|
-
|
12
|
-
def order
|
13
|
-
@order ||= (super || DEFAULT_ORDER)
|
5
|
+
offset = starting_offset + paged_nodes_array.index(item) + 1
|
6
|
+
Base64.strict_encode64(offset.to_s)
|
14
7
|
end
|
15
8
|
|
16
9
|
def has_next_page
|
@@ -26,12 +19,9 @@ module GraphQL
|
|
26
19
|
|
27
20
|
# apply first / last limit results
|
28
21
|
def paged_nodes
|
29
|
-
@paged_nodes
|
22
|
+
@paged_nodes ||= begin
|
30
23
|
items = sliced_nodes
|
31
|
-
limit
|
32
|
-
first && items = items.first(limit)
|
33
|
-
last && items.count > last && items = items.last(limit)
|
34
|
-
items
|
24
|
+
items.limit(limit)
|
35
25
|
end
|
36
26
|
end
|
37
27
|
|
@@ -39,61 +29,51 @@ module GraphQL
|
|
39
29
|
def sliced_nodes
|
40
30
|
@sliced_nodes ||= begin
|
41
31
|
items = object
|
42
|
-
|
43
|
-
if order
|
44
|
-
items = items.order(items.table[order_name].public_send(order_direction))
|
45
|
-
end
|
46
|
-
|
47
|
-
if after
|
48
|
-
_o, order_value = slice_from_cursor(after)
|
49
|
-
direction_marker = order_direction == :asc ? ">" : "<"
|
50
|
-
where_condition = create_order_condition(table_name, order_name, order_value, direction_marker)
|
51
|
-
items = items.where(where_condition)
|
52
|
-
end
|
53
|
-
|
54
|
-
if before
|
55
|
-
_o, order_value = slice_from_cursor(before)
|
56
|
-
direction_marker = order_direction == :asc ? "<" : ">"
|
57
|
-
where_condition = create_order_condition(table_name, order_name, order_value, direction_marker)
|
58
|
-
items = items.where(where_condition)
|
59
|
-
end
|
60
|
-
|
61
|
-
items
|
32
|
+
items.offset(starting_offset)
|
62
33
|
end
|
63
34
|
end
|
64
35
|
|
65
|
-
def
|
66
|
-
|
67
|
-
order, order_value = decoded.split(CURSOR_SEPARATOR)
|
36
|
+
def offset_from_cursor(cursor)
|
37
|
+
Base64.decode64(cursor).to_i
|
68
38
|
end
|
69
39
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
40
|
+
def starting_offset
|
41
|
+
@initial_offset ||= begin
|
42
|
+
if before
|
43
|
+
[previous_offset, 0].max
|
44
|
+
else
|
45
|
+
previous_offset
|
46
|
+
end
|
47
|
+
end
|
78
48
|
end
|
79
49
|
|
80
|
-
|
81
|
-
|
50
|
+
# Offset from the previous selection, if there was one
|
51
|
+
# Otherwise, zero
|
52
|
+
def previous_offset
|
53
|
+
@previous_offset ||= if after
|
54
|
+
offset_from_cursor(after)
|
55
|
+
elsif before
|
56
|
+
offset_from_cursor(before) - last - 1
|
57
|
+
else
|
58
|
+
0
|
59
|
+
end
|
82
60
|
end
|
83
61
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
table_name = ActiveRecord::Base.connection.quote_table_name(table)
|
88
|
-
name = ActiveRecord::Base.connection.quote_column_name(column)
|
89
|
-
if ActiveRecord::VERSION::MAJOR == 5
|
90
|
-
casted_value = object.table.able_to_type_cast? ? object.table.type_cast_for_database(column, value) : value
|
91
|
-
elsif ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2
|
92
|
-
casted_value = object.table.engine.columns_hash[column].cast_type.type_cast_from_user(value)
|
62
|
+
def limit
|
63
|
+
@limit ||= if first
|
64
|
+
[first, max_page_size].compact.min
|
93
65
|
else
|
94
|
-
|
66
|
+
last_limit = if previous_offset <= 0
|
67
|
+
previous_offset + last
|
68
|
+
else
|
69
|
+
last
|
70
|
+
end
|
71
|
+
[last_limit, max_page_size].compact.min
|
95
72
|
end
|
96
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
def paged_nodes_array
|
76
|
+
@paged_nodes_array ||= paged_nodes.to_a
|
97
77
|
end
|
98
78
|
end
|
99
79
|
|
@@ -11,9 +11,9 @@ describe GraphQL::Relay::ArrayConnection do
|
|
11
11
|
end
|
12
12
|
describe "results" do
|
13
13
|
let(:query_string) {%|
|
14
|
-
query getShips($first: Int, $after: String, $last: Int, $before: String, $
|
14
|
+
query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
|
15
15
|
rebels {
|
16
|
-
ships(first: $first, after: $after, last: $last, before: $before,
|
16
|
+
ships(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
|
17
17
|
edges {
|
18
18
|
cursor
|
19
19
|
node {
|
@@ -48,33 +48,23 @@ describe GraphQL::Relay::ArrayConnection do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
it 'slices the result' do
|
51
|
-
result = query(query_string, "first" =>
|
52
|
-
assert_equal(["X-Wing"
|
51
|
+
result = query(query_string, "first" => 1)
|
52
|
+
assert_equal(["X-Wing"], get_names(result))
|
53
53
|
|
54
54
|
# After the last result, find the next 2:
|
55
55
|
last_cursor = get_last_cursor(result)
|
56
56
|
|
57
57
|
result = query(query_string, "after" => last_cursor, "first" => 2)
|
58
|
-
assert_equal(["
|
59
|
-
|
60
|
-
result = query(query_string, "before" => last_cursor, "last" => 2)
|
61
|
-
assert_equal(["X-Wing", "Y-Wing"], get_names(result))
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'paginates with order' do
|
65
|
-
result = query(query_string, "first" => 2, "order" => "name")
|
66
|
-
assert_equal(["A-Wing", "Home One"], get_names(result))
|
58
|
+
assert_equal(["Y-Wing", "A-Wing"], get_names(result))
|
67
59
|
|
68
60
|
# After the last result, find the next 2:
|
69
|
-
last_cursor = result
|
61
|
+
last_cursor = get_last_cursor(result)
|
70
62
|
|
71
|
-
result = query(query_string, "after" => last_cursor, "first" => 2
|
72
|
-
assert_equal(["Millenium Falcon", "
|
73
|
-
end
|
63
|
+
result = query(query_string, "after" => last_cursor, "first" => 2)
|
64
|
+
assert_equal(["Millenium Falcon", "Home One"], get_names(result))
|
74
65
|
|
75
|
-
|
76
|
-
|
77
|
-
assert_equal(["Y-Wing", "X-Wing"], get_names(result))
|
66
|
+
result = query(query_string, "before" => last_cursor, "last" => 2)
|
67
|
+
assert_equal(["X-Wing", "Y-Wing"], get_names(result))
|
78
68
|
end
|
79
69
|
|
80
70
|
it 'applies custom arguments' do
|
@@ -83,7 +73,7 @@ describe GraphQL::Relay::ArrayConnection do
|
|
83
73
|
assert_equal(2, names.length)
|
84
74
|
|
85
75
|
after = get_last_cursor(result)
|
86
|
-
result = query(query_string, "nameIncludes" => "Wing", "after" => after)
|
76
|
+
result = query(query_string, "nameIncludes" => "Wing", "after" => after, "first" => 3)
|
87
77
|
names = get_names(result)
|
88
78
|
assert_equal(1, names.length)
|
89
79
|
end
|
@@ -15,9 +15,9 @@ describe GraphQL::Relay::PageInfo do
|
|
15
15
|
}
|
16
16
|
|
17
17
|
let(:query_string) {%|
|
18
|
-
query getShips($first: Int, $after: String, $last: Int, $before: String, $
|
18
|
+
query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
|
19
19
|
empire {
|
20
|
-
bases(first: $first, after: $after, last: $last, before: $before,
|
20
|
+
bases(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
|
21
21
|
edges {
|
22
22
|
cursor
|
23
23
|
}
|
@@ -12,9 +12,9 @@ describe GraphQL::Relay::RelationConnection do
|
|
12
12
|
|
13
13
|
describe "results" do
|
14
14
|
let(:query_string) {%|
|
15
|
-
query getShips($first: Int, $after: String, $last: Int, $before: String,
|
15
|
+
query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
|
16
16
|
empire {
|
17
|
-
bases(first: $first, after: $after, last: $last, before: $before,
|
17
|
+
bases(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
|
18
18
|
... basesConnection
|
19
19
|
}
|
20
20
|
}
|
@@ -58,27 +58,16 @@ describe GraphQL::Relay::RelationConnection do
|
|
58
58
|
assert_equal(["Headquarters"], get_names(result))
|
59
59
|
|
60
60
|
last_cursor = get_last_cursor(result)
|
61
|
+
|
61
62
|
result = query(query_string, "before" => last_cursor, "last" => 1)
|
62
63
|
assert_equal(["Shield Generator"], get_names(result))
|
63
64
|
|
64
65
|
result = query(query_string, "before" => last_cursor, "last" => 2)
|
65
66
|
assert_equal(["Death Star", "Shield Generator"], get_names(result))
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'paginates with reverse order' do
|
69
|
-
result = query(query_string, "first" => 2, "order" => "-name")
|
70
|
-
assert_equal(["Shield Generator", "Headquarters"], get_names(result))
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'paginates with order' do
|
74
|
-
result = query(query_string, "first" => 2, "order" => "name")
|
75
|
-
assert_equal(["Death Star", "Headquarters"], get_names(result))
|
76
67
|
|
77
|
-
|
78
|
-
|
68
|
+
result = query(query_string, "before" => last_cursor, "last" => 10)
|
69
|
+
assert_equal(["Death Star", "Shield Generator"], get_names(result))
|
79
70
|
|
80
|
-
result = query(query_string, "after" => last_cursor, "first" => 2, "order" => "name")
|
81
|
-
assert_equal(["Shield Generator"], get_names(result))
|
82
71
|
end
|
83
72
|
|
84
73
|
it "applies custom arguments" do
|
@@ -138,7 +127,7 @@ describe GraphQL::Relay::RelationConnection do
|
|
138
127
|
let(:query_string) {%|
|
139
128
|
{
|
140
129
|
empire {
|
141
|
-
basesClone {
|
130
|
+
basesClone(first: 10) {
|
142
131
|
edges {
|
143
132
|
node {
|
144
133
|
name
|
@@ -154,12 +143,12 @@ describe GraphQL::Relay::RelationConnection do
|
|
154
143
|
end
|
155
144
|
end
|
156
145
|
|
157
|
-
describe "
|
146
|
+
describe "custom ordering" do
|
158
147
|
let(:query_string) {%|
|
159
148
|
query getBases {
|
160
149
|
empire {
|
161
|
-
basesByName { ... basesFields }
|
162
|
-
bases { ... basesFields }
|
150
|
+
basesByName(first: 30) { ... basesFields }
|
151
|
+
bases(first: 30) { ... basesFields }
|
163
152
|
}
|
164
153
|
}
|
165
154
|
fragment basesFields on BaseConnection {
|
@@ -87,6 +87,13 @@ Faction = GraphQL::ObjectType.define do
|
|
87
87
|
connection :basesClone, BaseType.connection_type
|
88
88
|
connection :basesByName, BaseType.connection_type, property: :bases do
|
89
89
|
argument :order, types.String, default_value: "name"
|
90
|
+
resolve -> (obj, args, ctx) {
|
91
|
+
if args[:order].present?
|
92
|
+
obj.bases.order(args[:order])
|
93
|
+
else
|
94
|
+
obj.bases
|
95
|
+
end
|
96
|
+
}
|
90
97
|
end
|
91
98
|
|
92
99
|
connection :basesWithMaxLimitRelation, BaseType.connection_type, max_page_size: 2 do
|
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.9.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-03-
|
11
|
+
date: 2016-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -200,6 +200,7 @@ executables: []
|
|
200
200
|
extensions: []
|
201
201
|
extra_rdoc_files: []
|
202
202
|
files:
|
203
|
+
- MIT-LICENSE
|
203
204
|
- README.md
|
204
205
|
- lib/graphql/relay.rb
|
205
206
|
- lib/graphql/relay/array_connection.rb
|