graphql-connections 0.1.0 → 1.1.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 +58 -3
- data/lib/graphql/connections.rb +9 -0
- data/lib/graphql/connections/base.rb +65 -0
- data/lib/graphql/connections/chewy.rb +37 -0
- data/lib/graphql/connections/key_asc.rb +69 -0
- data/lib/graphql/connections/keyset/asc.rb +85 -0
- data/lib/graphql/connections/keyset/base.rb +59 -0
- data/lib/graphql/connections/keyset/desc.rb +92 -0
- data/lib/graphql/connections/stable.rb +8 -63
- data/lib/graphql/connections/version.rb +1 -1
- metadata +65 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef27f5fb3f9d555769a1328f85b342b65b472e2d282a0e46296ba91441572641
|
|
4
|
+
data.tar.gz: dc5a4b2884b5266acad35c37b400fade1ea3850beaa2a03eb2d2974276bcd02a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b7ba37f332c49b5238969a683c1cdfbdcec8c76074d1e3d3903e1d2f8668bed48ea069a55cd65ed49f8ee6dceb7b5022dbfee2d95b1ca8452bb15d1b5d04053
|
|
7
|
+
data.tar.gz: 5375969fb009a1e7e7691f19fab5c0c60befc071d4cebf5e73a7854831508ee240e9209daeb4c33cf1f972cda56524fc97862105d2b0e8c0161a14e142fc3f3c
|
data/README.md
CHANGED
|
@@ -21,6 +21,8 @@ gem "graphql-connections"
|
|
|
21
21
|
|
|
22
22
|
## Usage
|
|
23
23
|
|
|
24
|
+
### ActiveRecord
|
|
25
|
+
|
|
24
26
|
You can use a stable connection wrapper on a specific field:
|
|
25
27
|
|
|
26
28
|
```ruby
|
|
@@ -31,17 +33,70 @@ def messages
|
|
|
31
33
|
end
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
Records are sorted by model's primary key by default. You can change this behaviour by providing `primary_key` param:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
GraphQL::Connections::Stable.new(Message.all, primary_key: :created_at)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
In case when you want records to be sorted by more than one field (i.e., _keyset pagination_), you can use `keys` param:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
GraphQL::Connections::Stable.new(Message.all, keys: %w[name id])
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When you pass only one key, a primary key will be added as a second one:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
GraphQL::Connections::Stable.new(Message.all, keys: [:name])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**NOTE:** Currently we support maximum two keys in the keyset.
|
|
55
|
+
|
|
56
|
+
Also, you can pass the `:desc` option to reverse the relation:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
GraphQL::Connections::Stable.new(Message.all, keys: %w[name id], desc: true)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**NOTE:** `:desc` option is not implemented for stable connections with `:primary_key` passed; if you need it—use keyset pagination or implement `:desc` option for us 🙂.
|
|
63
|
+
|
|
64
|
+
Also, you can disable opaque cursors by setting `opaque_cursor` param:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
GraphQL::Connections::Stable.new(Message.all, opaque_cursor: false)
|
|
68
|
+
```
|
|
69
|
+
|
|
34
70
|
Or you can apply a stable connection to all Active Record relations returning by any field:
|
|
35
71
|
|
|
36
72
|
```ruby
|
|
37
73
|
class ApplicationSchema < GraphQL::Schema
|
|
38
|
-
use GraphQL::Pagination::Connections
|
|
39
|
-
|
|
40
74
|
connections.add(ActiveRecord::Relation, GraphQL::Connections::Stable)
|
|
41
75
|
end
|
|
42
76
|
```
|
|
43
77
|
|
|
44
|
-
**NOTE:** Don't use stable connections for relations whose ordering is too complicated for cursor generation.
|
|
78
|
+
**NOTE:** Don't use stable connections for relations whose ordering is too complicated for cursor generation.
|
|
79
|
+
|
|
80
|
+
### Elasticsearch via Chewy
|
|
81
|
+
|
|
82
|
+
Register connection for all Chewy requests:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class ApplicationSchema < GraphQL::Schema
|
|
86
|
+
connections.add(Chewy::Search::Request, GraphQL::Connections::ChewyConnection)
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
And define field like below:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
field :messages, Types::Message.connection_type, null: false
|
|
94
|
+
|
|
95
|
+
def messages
|
|
96
|
+
CitiesIndex.query(match: {name: "Moscow"})
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
**NOTE:** Using `first` and `last`arguments simultaneously is not supported yet.
|
|
45
100
|
|
|
46
101
|
## Development
|
|
47
102
|
|
data/lib/graphql/connections.rb
CHANGED
|
@@ -9,3 +9,12 @@ module GraphQL
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
require "graphql/connections/stable"
|
|
12
|
+
|
|
13
|
+
require "graphql/connections/base"
|
|
14
|
+
require "graphql/connections/key_asc"
|
|
15
|
+
|
|
16
|
+
require "graphql/connections/keyset/base"
|
|
17
|
+
require "graphql/connections/keyset/asc"
|
|
18
|
+
require "graphql/connections/keyset/desc"
|
|
19
|
+
|
|
20
|
+
require "graphql/connections/chewy"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Connections
|
|
5
|
+
# Base class for pagination implementations
|
|
6
|
+
class Base < ::GraphQL::Pagination::Connection
|
|
7
|
+
attr_reader :opaque_cursor
|
|
8
|
+
|
|
9
|
+
delegate :arel_table, to: :items
|
|
10
|
+
|
|
11
|
+
def initialize(*args, opaque_cursor: true, **kwargs)
|
|
12
|
+
@opaque_cursor = opaque_cursor
|
|
13
|
+
|
|
14
|
+
super(*args, **kwargs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def primary_key
|
|
18
|
+
@primary_key ||= items.model.primary_key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def nodes
|
|
22
|
+
@nodes ||= limited_relation
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_previous_page # rubocop:disable Naming/PredicateName
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def has_next_page # rubocop:disable Naming/PredicateName
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cursor_for(item)
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def serialize(cursor)
|
|
40
|
+
case cursor
|
|
41
|
+
when Time, DateTime, Date
|
|
42
|
+
cursor.iso8601
|
|
43
|
+
else
|
|
44
|
+
cursor.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def limited_relation
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sliced_relation
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def after_cursor
|
|
57
|
+
@after_cursor ||= opaque_cursor ? decode(after) : after
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def before_cursor
|
|
61
|
+
@before_cursor ||= opaque_cursor ? decode(before) : before
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Connections
|
|
5
|
+
class Chewy < ::GraphQL::Pagination::RelationConnection
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def load_nodes
|
|
9
|
+
@nodes ||= limited_nodes.objects
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def relation_count(relation)
|
|
13
|
+
offset = relation_offset(relation) || 0
|
|
14
|
+
limit = relation_limit(relation)
|
|
15
|
+
count = relation.count - offset
|
|
16
|
+
|
|
17
|
+
if limit.nil?
|
|
18
|
+
count
|
|
19
|
+
else
|
|
20
|
+
[count, limit].min
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def relation_limit(relation)
|
|
25
|
+
relation.send(:raw_limit_value)&.to_i
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def relation_offset(relation)
|
|
29
|
+
relation.send(:raw_offset_value)&.to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def null_relation(relation)
|
|
33
|
+
relation.none
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Connections
|
|
5
|
+
# Implements pagination by one field with asc order
|
|
6
|
+
class KeyAsc < ::GraphQL::Connections::Base
|
|
7
|
+
def initialize(*args, primary_key: nil, **kwargs)
|
|
8
|
+
@primary_key = primary_key
|
|
9
|
+
|
|
10
|
+
super(*args, **kwargs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def has_previous_page # rubocop:disable Naming/PredicateName, Metrics/AbcSize
|
|
14
|
+
if last
|
|
15
|
+
nodes.any? && items.where(arel_table[primary_key].lt(nodes.first[primary_key])).exists?
|
|
16
|
+
elsif after
|
|
17
|
+
items.where(arel_table[primary_key].lteq(after_cursor)).exists?
|
|
18
|
+
else
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def has_next_page # rubocop:disable Naming/PredicateName, Metrics/AbcSize
|
|
24
|
+
if first
|
|
25
|
+
nodes.any? && items.where(arel_table[primary_key].gt(nodes.last[primary_key])).exists?
|
|
26
|
+
elsif before
|
|
27
|
+
items.where(arel_table[primary_key].gteq(before_cursor)).exists?
|
|
28
|
+
else
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cursor_for(item)
|
|
34
|
+
cursor = serialize(item[primary_key])
|
|
35
|
+
cursor = encode(cursor) if opaque_cursor
|
|
36
|
+
cursor
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def limited_relation # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
42
|
+
scope = sliced_relation
|
|
43
|
+
nodes = []
|
|
44
|
+
|
|
45
|
+
if first
|
|
46
|
+
nodes |= scope
|
|
47
|
+
.reorder(arel_table[primary_key].asc)
|
|
48
|
+
.limit(first)
|
|
49
|
+
.to_a
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if last
|
|
53
|
+
nodes |= scope
|
|
54
|
+
.reorder(arel_table[primary_key].desc)
|
|
55
|
+
.limit(last)
|
|
56
|
+
.to_a.reverse!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
nodes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sliced_relation # rubocop:disable Metrics/AbcSize
|
|
63
|
+
items
|
|
64
|
+
.yield_self { |s| after ? s.where(arel_table[primary_key].gt(after_cursor)) : s }
|
|
65
|
+
.yield_self { |s| before ? s.where(arel_table[primary_key].lt(before_cursor)) : s }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Connections
|
|
5
|
+
module Keyset
|
|
6
|
+
# Implements keyset pagination by two fields with asc order
|
|
7
|
+
class Asc < ::GraphQL::Connections::Keyset::Base
|
|
8
|
+
# rubocop:disable Naming/PredicateName, Metrics/AbcSize, Metrics/MethodLength
|
|
9
|
+
def has_previous_page
|
|
10
|
+
if last
|
|
11
|
+
nodes.any? &&
|
|
12
|
+
items.where(arel_table[field_key].eq(nodes.first[field_key]))
|
|
13
|
+
.where(arel_table[primary_key].lt(nodes.first[primary_key]))
|
|
14
|
+
.or(items.where(arel_table[field_key].lt(nodes.first[field_key])))
|
|
15
|
+
.exists?
|
|
16
|
+
elsif after
|
|
17
|
+
items
|
|
18
|
+
.where(arel_table[field_key].lt(after_cursor_date))
|
|
19
|
+
.or(
|
|
20
|
+
items.where(arel_table[field_key].eq(after_cursor_date))
|
|
21
|
+
.where(arel_table[primary_key].lt(after_cursor_primary_key))
|
|
22
|
+
).exists?
|
|
23
|
+
else
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_next_page
|
|
29
|
+
if first
|
|
30
|
+
nodes.any? &&
|
|
31
|
+
items.where(arel_table[field_key].eq(nodes.last[field_key]))
|
|
32
|
+
.where(arel_table[primary_key].gt(nodes.last[primary_key]))
|
|
33
|
+
.or(items.where(arel_table[field_key].gt(nodes.last[field_key])))
|
|
34
|
+
.exists?
|
|
35
|
+
elsif before
|
|
36
|
+
items
|
|
37
|
+
.where(arel_table[field_key].gt(before_cursor_date))
|
|
38
|
+
.or(
|
|
39
|
+
items.where(arel_table[field_key].eq(before_cursor_date))
|
|
40
|
+
.where(arel_table[primary_key].gt(before_cursor_primary_key))
|
|
41
|
+
).exists?
|
|
42
|
+
else
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# rubocop:enable Naming/PredicateName, Metrics/AbcSize, Metrics/MethodLength
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def limited_relation # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
51
|
+
scope = sliced_relation
|
|
52
|
+
nodes = []
|
|
53
|
+
|
|
54
|
+
if first
|
|
55
|
+
nodes |= scope
|
|
56
|
+
.reorder(arel_table[field_key].asc, arel_table[primary_key].asc)
|
|
57
|
+
.limit(first).to_a
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if last
|
|
61
|
+
nodes |= scope
|
|
62
|
+
.reorder(arel_table[field_key].desc, arel_table[primary_key].desc)
|
|
63
|
+
.limit(last).to_a.reverse!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
nodes
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def sliced_relation_after(relation) # rubocop:disable Metrics/AbcSize
|
|
70
|
+
relation
|
|
71
|
+
.where(arel_table[field_key].eq(after_cursor_date))
|
|
72
|
+
.where(arel_table[primary_key].gt(after_cursor_primary_key))
|
|
73
|
+
.or(relation.where(arel_table[field_key].gt(after_cursor_date)))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def sliced_relation_before(relation) # rubocop:disable Metrics/AbcSize
|
|
77
|
+
relation
|
|
78
|
+
.where(arel_table[field_key].eq(before_cursor_date))
|
|
79
|
+
.where(arel_table[primary_key].lt(before_cursor_primary_key))
|
|
80
|
+
.or(relation.where(arel_table[field_key].lt(before_cursor_date)))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Connections
|
|
5
|
+
module Keyset
|
|
6
|
+
# Base class for keyset pagination implementations
|
|
7
|
+
class Base < ::GraphQL::Connections::Base
|
|
8
|
+
attr_reader :field_key
|
|
9
|
+
|
|
10
|
+
SEPARATOR = "/"
|
|
11
|
+
|
|
12
|
+
def initialize(*args, keys:, separator: SEPARATOR, **kwargs)
|
|
13
|
+
@field_key, @primary_key = keys
|
|
14
|
+
@separator = separator
|
|
15
|
+
|
|
16
|
+
super(*args, **kwargs)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cursor_for(item)
|
|
20
|
+
cursor = [item[field_key], item[primary_key]].map { |value| serialize(value) }.join(@separator)
|
|
21
|
+
cursor = encode(cursor) if opaque_cursor
|
|
22
|
+
cursor
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def sliced_relation
|
|
28
|
+
items
|
|
29
|
+
.yield_self { |s| after ? sliced_relation_after(s) : s }
|
|
30
|
+
.yield_self { |s| before ? sliced_relation_before(s) : s }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def after_cursor_date
|
|
34
|
+
@after_cursor_date ||= after_cursor.first
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def after_cursor_primary_key
|
|
38
|
+
@after_cursor_primary_key ||= after_cursor.last
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def after_cursor
|
|
42
|
+
@after_cursor ||= super.split(SEPARATOR)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def before_cursor_date
|
|
46
|
+
@before_cursor_date ||= before_cursor.first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def before_cursor_primary_key
|
|
50
|
+
@before_cursor_primary_key ||= before_cursor.last
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def before_cursor
|
|
54
|
+
@before_cursor ||= super.split(SEPARATOR)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Connections
|
|
5
|
+
module Keyset
|
|
6
|
+
# Implements keyset pagination by two fields with desc order
|
|
7
|
+
class Desc < ::GraphQL::Connections::Keyset::Base
|
|
8
|
+
# rubocop:disable Naming/PredicateName, Metrics/AbcSize, Metrics/MethodLength
|
|
9
|
+
def has_previous_page
|
|
10
|
+
if last
|
|
11
|
+
nodes.any? &&
|
|
12
|
+
items.where(arel_table[field_key].eq(nodes.first[field_key]))
|
|
13
|
+
.where(arel_table[primary_key].gt(nodes.first[primary_key]))
|
|
14
|
+
.or(items.where(arel_table[field_key].gt(nodes.first[field_key])))
|
|
15
|
+
.exists?
|
|
16
|
+
elsif after
|
|
17
|
+
items
|
|
18
|
+
.where(arel_table[field_key].gt(after_cursor_date))
|
|
19
|
+
.or(
|
|
20
|
+
items.where(arel_table[field_key].eq(after_cursor_date))
|
|
21
|
+
.where(arel_table[primary_key].gt(after_cursor_primary_key))
|
|
22
|
+
).exists?
|
|
23
|
+
else
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_next_page
|
|
29
|
+
if first
|
|
30
|
+
nodes.any? &&
|
|
31
|
+
items.where(arel_table[field_key].eq(nodes.last[field_key]))
|
|
32
|
+
.where(arel_table[primary_key].lt(nodes.last[primary_key]))
|
|
33
|
+
.or(items.where(arel_table[field_key].lt(nodes.last[field_key])))
|
|
34
|
+
.exists?
|
|
35
|
+
elsif before
|
|
36
|
+
items
|
|
37
|
+
.where(arel_table[field_key].lt(before_cursor_date))
|
|
38
|
+
.or(
|
|
39
|
+
items.where(arel_table[field_key].eq(before_cursor_date))
|
|
40
|
+
.where(arel_table[primary_key].lt(before_cursor_primary_key))
|
|
41
|
+
).exists?
|
|
42
|
+
else
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# rubocop:enable Naming/PredicateName, Metrics/AbcSize, Metrics/MethodLength
|
|
47
|
+
|
|
48
|
+
def cursor_for(item)
|
|
49
|
+
cursor = [item[field_key], item[primary_key]].map { |value| serialize(value) }.join(@separator)
|
|
50
|
+
cursor = encode(cursor) if opaque_cursor
|
|
51
|
+
cursor
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def limited_relation # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
57
|
+
scope = sliced_relation
|
|
58
|
+
nodes = []
|
|
59
|
+
|
|
60
|
+
if first
|
|
61
|
+
nodes |= scope
|
|
62
|
+
.reorder(arel_table[field_key].desc, arel_table[primary_key].desc)
|
|
63
|
+
.limit(first).to_a
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if last
|
|
67
|
+
nodes |= scope
|
|
68
|
+
.reorder(arel_table[field_key].asc, arel_table[primary_key].asc)
|
|
69
|
+
.limit(last)
|
|
70
|
+
.to_a.reverse!
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
nodes
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def sliced_relation_after(relation) # rubocop:disable Metrics/AbcSize
|
|
77
|
+
relation
|
|
78
|
+
.where(arel_table[field_key].eq(after_cursor_date))
|
|
79
|
+
.where(arel_table[primary_key].lt(after_cursor_primary_key))
|
|
80
|
+
.or(relation.where(arel_table[field_key].lt(after_cursor_date)))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def sliced_relation_before(relation) # rubocop:disable Metrics/AbcSize
|
|
84
|
+
relation
|
|
85
|
+
.where(arel_table[field_key].eq(before_cursor_date))
|
|
86
|
+
.where(arel_table[primary_key].gt(before_cursor_primary_key))
|
|
87
|
+
.or(relation.where(arel_table[field_key].gt(before_cursor_date)))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -11,73 +11,18 @@ module GraphQL
|
|
|
11
11
|
#
|
|
12
12
|
# For more information see GraphQL Cursor Connections Specification
|
|
13
13
|
# https://relay.dev/graphql/connections.htm
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
module Stable
|
|
15
|
+
def self.new(*args, desc: false, keys: nil, **kwargs)
|
|
16
|
+
if kwargs[:primary_key] || keys.nil?
|
|
17
|
+
raise NotImplementedError, "desc connection is not implemented yet" if desc
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
@nodes ||= limited_relation
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def has_previous_page # rubocop:disable Naming/PredicateName
|
|
23
|
-
if last
|
|
24
|
-
nodes.any? && items.where(arel_table[primary_key].lt(nodes.first.id)).exists?
|
|
25
|
-
elsif after
|
|
26
|
-
items.where(arel_table[primary_key].lteq(after_cursor)).exists?
|
|
27
|
-
else
|
|
28
|
-
false
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def has_next_page # rubocop:disable Naming/PredicateName
|
|
33
|
-
if first
|
|
34
|
-
nodes.any? && items.where(arel_table[primary_key].gt(nodes.last.id)).exists?
|
|
35
|
-
elsif before
|
|
36
|
-
items.where(arel_table[primary_key].gteq(before_cursor)).exists?
|
|
37
|
-
else
|
|
38
|
-
false
|
|
19
|
+
return GraphQL::Connections::KeyAsc.new(*args, **kwargs)
|
|
39
20
|
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def cursor_for(item)
|
|
43
|
-
encode(item.id.to_s)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def limited_relation
|
|
49
|
-
scope = sliced_relation
|
|
50
|
-
nodes = []
|
|
51
21
|
|
|
52
|
-
if
|
|
53
|
-
nodes |= scope.
|
|
54
|
-
reorder(arel_table[primary_key].asc).
|
|
55
|
-
limit(first).
|
|
56
|
-
to_a
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
if last
|
|
60
|
-
nodes |= scope.
|
|
61
|
-
reorder(arel_table[primary_key].desc).
|
|
62
|
-
limit(last).
|
|
63
|
-
to_a.reverse!
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
nodes
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def sliced_relation
|
|
70
|
-
items.
|
|
71
|
-
yield_self { |s| after ? s.where(arel_table[primary_key].gt(after_cursor)) : s }.
|
|
72
|
-
yield_self { |s| before ? s.where(arel_table[primary_key].lt(before_cursor)) : s }
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def after_cursor
|
|
76
|
-
@after_cursor ||= decode(after)
|
|
77
|
-
end
|
|
22
|
+
raise ArgumentError, "keyset for more that 2 keys is not implemented yet" if keys.length > 2
|
|
78
23
|
|
|
79
|
-
|
|
80
|
-
|
|
24
|
+
klass = desc ? GraphQL::Connections::Keyset::Desc : GraphQL::Connections::Keyset::Asc
|
|
25
|
+
klass.new(*args, **kwargs.merge(keys: keys))
|
|
81
26
|
end
|
|
82
27
|
end
|
|
83
28
|
end
|
metadata
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql-connections
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Misha Merkushin
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-07-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '5'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '5'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
@@ -52,6 +52,48 @@ dependencies:
|
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '1.16'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: factory_bot_rails
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '6.2'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '6.2'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: faker
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2.7'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.7'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: chewy
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '7.2'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '7.2'
|
|
55
97
|
- !ruby/object:Gem::Dependency
|
|
56
98
|
name: combustion
|
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -114,43 +156,43 @@ dependencies:
|
|
|
114
156
|
requirements:
|
|
115
157
|
- - "~>"
|
|
116
158
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
159
|
+
version: '5.0'
|
|
118
160
|
type: :development
|
|
119
161
|
prerelease: false
|
|
120
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
163
|
requirements:
|
|
122
164
|
- - "~>"
|
|
123
165
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '
|
|
166
|
+
version: '5.0'
|
|
125
167
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
168
|
+
name: standard
|
|
127
169
|
requirement: !ruby/object:Gem::Requirement
|
|
128
170
|
requirements:
|
|
129
171
|
- - "~>"
|
|
130
172
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '
|
|
173
|
+
version: '1.1'
|
|
132
174
|
type: :development
|
|
133
175
|
prerelease: false
|
|
134
176
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
177
|
requirements:
|
|
136
178
|
- - "~>"
|
|
137
179
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '
|
|
180
|
+
version: '1.1'
|
|
139
181
|
- !ruby/object:Gem::Dependency
|
|
140
182
|
name: test-prof
|
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
|
142
184
|
requirements:
|
|
143
185
|
- - "~>"
|
|
144
186
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0
|
|
187
|
+
version: '1.0'
|
|
146
188
|
type: :development
|
|
147
189
|
prerelease: false
|
|
148
190
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
191
|
requirements:
|
|
150
192
|
- - "~>"
|
|
151
193
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0
|
|
153
|
-
description:
|
|
194
|
+
version: '1.0'
|
|
195
|
+
description:
|
|
154
196
|
email:
|
|
155
197
|
- merkushin.m.s@gmail.com
|
|
156
198
|
executables: []
|
|
@@ -160,6 +202,12 @@ files:
|
|
|
160
202
|
- LICENSE.txt
|
|
161
203
|
- README.md
|
|
162
204
|
- lib/graphql/connections.rb
|
|
205
|
+
- lib/graphql/connections/base.rb
|
|
206
|
+
- lib/graphql/connections/chewy.rb
|
|
207
|
+
- lib/graphql/connections/key_asc.rb
|
|
208
|
+
- lib/graphql/connections/keyset/asc.rb
|
|
209
|
+
- lib/graphql/connections/keyset/base.rb
|
|
210
|
+
- lib/graphql/connections/keyset/desc.rb
|
|
163
211
|
- lib/graphql/connections/stable.rb
|
|
164
212
|
- lib/graphql/connections/version.rb
|
|
165
213
|
homepage: https://github.com/bibendi/graphql-connections
|
|
@@ -167,15 +215,15 @@ licenses:
|
|
|
167
215
|
- MIT
|
|
168
216
|
metadata:
|
|
169
217
|
allowed_push_host: https://rubygems.org
|
|
170
|
-
post_install_message:
|
|
218
|
+
post_install_message:
|
|
171
219
|
rdoc_options: []
|
|
172
220
|
require_paths:
|
|
173
221
|
- lib
|
|
174
222
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
175
223
|
requirements:
|
|
176
|
-
- - "
|
|
224
|
+
- - ">"
|
|
177
225
|
- !ruby/object:Gem::Version
|
|
178
|
-
version: '
|
|
226
|
+
version: '2.5'
|
|
179
227
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
228
|
requirements:
|
|
181
229
|
- - ">="
|
|
@@ -183,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
183
231
|
version: '0'
|
|
184
232
|
requirements: []
|
|
185
233
|
rubygems_version: 3.1.2
|
|
186
|
-
signing_key:
|
|
234
|
+
signing_key:
|
|
187
235
|
specification_version: 4
|
|
188
236
|
summary: GraphQL cursor-based stable pagination to work with Active Record relations
|
|
189
237
|
test_files: []
|