graphql-connections 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1206b9dbdd7d135f43b52b0a62b4d28b4d5d8c93bb5e8a18800ef5abcc5a6157
4
- data.tar.gz: 509ba8323078d1dee9095c18ec227b8416967ad0ce51bb4e3bd2806f09a28a57
3
+ metadata.gz: '02926f990b7d1e58b3525574aa596bb9562c505748b5ec57bc6273c6b5f1392d'
4
+ data.tar.gz: 27b7eadc1484abdca5f63689a7f12a9ae342b4067701263c7c7e2a64d72ed575
5
5
  SHA512:
6
- metadata.gz: 63f191221ff5b1e2133ad84bb8cb377941cad75de324310f18734729f5a519dae01f466a88539e895a155f32e39eccfab60f423271a220b4cf3fcfa33bfdbb3b
7
- data.tar.gz: da4d990bcc8941fbce16049f0a4ea6aa642f73194a34bfb757c1c0080bd16da1736e4466f2ee7f2868dcc5ceebc8677b745a6c3ea43328561af8ad09f9c9e71f
6
+ metadata.gz: b23023ada99877a5a48fc7d5ae9c516a7ff85e0047ba1aa552b46c8b9bfc97e8fb68554cad0f8fefd5e23a76d4040f9e9a79c342709a8a97e6465e76cd4b541a
7
+ data.tar.gz: 7407e8990df1428fc8accfcbedca133fea7847e9052bd6aed828eb6dfbf5101166a18741e4e54033498898da6014ccaaa9150ffdd14b02fe0ba9039d6701487a
data/README.md CHANGED
@@ -59,7 +59,9 @@ Also, you can pass the `:desc` option to reverse the relation:
59
59
  GraphQL::Connections::Stable.new(Message.all, keys: %w[name id], desc: true)
60
60
  ```
61
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 🙂.
62
+ ```ruby
63
+ GraphQL::Connections::Stable.new(Message.all, primary_key: :created_at, desc: true)
64
+ ```
63
65
 
64
66
  Also, you can disable opaque cursors by setting `opaque_cursor` param:
65
67
 
@@ -42,10 +42,10 @@ module GraphQL
42
42
  false
43
43
  end
44
44
  end
45
- # rubocop:enable Naming/PredicateName, Metrics/AbcSize, Metrics/MethodLength
46
45
 
47
46
  private
48
47
 
48
+ # standard:disable Metrics/AbcSize, Metrics/MethodLength
49
49
  def limited_relation
50
50
  scope = sliced_relation
51
51
  nodes = []
@@ -78,6 +78,7 @@ module GraphQL
78
78
  .where(arel_table[primary_key].lt(before_cursor_primary_key))
79
79
  .or(relation.where(arel_table[field_key].lt(before_cursor_date)))
80
80
  end
81
+ # standard:enable Metrics/AbcSize, Metrics/MethodLength
81
82
  end
82
83
  end
83
84
  end
@@ -42,8 +42,8 @@ module GraphQL
42
42
  false
43
43
  end
44
44
  end
45
- # rubocop:enable Naming/PredicateName, Metrics/AbcSize, Metrics/MethodLength
46
45
 
46
+ # standard:disable Metrics/AbcSize, Metrics/MethodLength
47
47
  def cursor_for(item)
48
48
  cursor = [item[field_key], item[primary_key]].map { |value| serialize(value) }.join(@separator)
49
49
  cursor = encode(cursor) if opaque_cursor
@@ -85,6 +85,7 @@ module GraphQL
85
85
  .where(arel_table[primary_key].gt(before_cursor_primary_key))
86
86
  .or(relation.where(arel_table[field_key].gt(before_cursor_date)))
87
87
  end
88
+ # standard:disable Metrics/AbcSize, Metrics/MethodLength
88
89
  end
89
90
  end
90
91
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Connections
5
+ # Implements pagination by one field with asc order
6
+ module PrimaryKey
7
+ class Asc < Base
8
+ PAGE_COMPARABLE_METHODS = {
9
+ previous: {query: :lt, cursor: :lteq},
10
+ next: {query: :gt, cursor: :gteq}
11
+ }
12
+
13
+ SLICED_COMPARABLE_METHODS = {
14
+ after: :gt,
15
+ before: :lt
16
+ }.freeze
17
+
18
+ private
19
+
20
+ def first_limited_sorted_table
21
+ arel_table[primary_key].asc
22
+ end
23
+
24
+ def last_limited_sorted_table
25
+ arel_table[primary_key].desc
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Connections
5
+ module PrimaryKey
6
+ # Base class for PrimaryKey pagination implementations
7
+ class Base < ::GraphQL::Connections::Base
8
+ COMPARABLE_METHODS = %i[
9
+ gt lt lteq gteq
10
+ ].freeze
11
+
12
+ def initialize(*args, primary_key: nil, **kwargs)
13
+ @primary_key = primary_key
14
+
15
+ super(*args, **kwargs)
16
+ end
17
+
18
+ def has_previous_page
19
+ return false if nodes.empty?
20
+
21
+ if last
22
+ items_exist?(type: :query, search: nodes.first[primary_key], page_type: :previous)
23
+ elsif after
24
+ items_exist?(type: :cursor, search: after_cursor, page_type: :previous)
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ def has_next_page
31
+ return false if nodes.empty?
32
+
33
+ if first
34
+ items_exist?(type: :query, search: nodes.last[primary_key], page_type: :next)
35
+ elsif before
36
+ items_exist?(type: :cursor, search: before_cursor, page_type: :next)
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ def cursor_for(item)
43
+ cursor = serialize(item[primary_key])
44
+ cursor = encode(cursor) if opaque_cursor
45
+ cursor
46
+ end
47
+
48
+ private
49
+
50
+ def page_comparable_method(query_type:, page_type:)
51
+ self.class::PAGE_COMPARABLE_METHODS.fetch(page_type).fetch(query_type)
52
+ end
53
+
54
+ def items_exist?(type:, search:, page_type:)
55
+ comparable_method = page_comparable_method(query_type: type, page_type: page_type)
56
+
57
+ if COMPARABLE_METHODS.exclude?(comparable_method)
58
+ raise ArgumentError.new("Unknown #{comparable_method} comparable type. Allowed #{COMPARABLE_METHODS.join(", ")}")
59
+ end
60
+
61
+ items.where(arel_table[primary_key].send(comparable_method, search)).exists?
62
+ end
63
+
64
+ def limited_relation
65
+ scope = sliced_relation
66
+ nodes = []
67
+
68
+ if first
69
+ nodes |=
70
+ scope
71
+ .reorder(first_limited_sorted_table)
72
+ .limit(first)
73
+ .to_a
74
+ end
75
+
76
+ if last
77
+ nodes |=
78
+ scope
79
+ .reorder(last_limited_sorted_table)
80
+ .limit(last)
81
+ .to_a.reverse!
82
+ end
83
+
84
+ nodes
85
+ end
86
+
87
+ def sliced_relation
88
+ items
89
+ .yield_self { |s| after ? sliced_items(items: s, cursor: after_cursor, type: :after) : s }
90
+ .yield_self { |s| before ? sliced_items(items: s, cursor: before_cursor, type: :before) : s }
91
+ end
92
+
93
+ def sliced_items(items:, cursor:, type:)
94
+ items.where(arel_table[primary_key].send(sliced_comparable_method(type), cursor))
95
+ end
96
+
97
+ def sliced_comparable_method(type)
98
+ self.class::SLICED_COMPARABLE_METHODS.fetch(type.to_sym)
99
+ end
100
+
101
+ def first_limited_sorted_table
102
+ raise NotImplementedError.new("method \"#{__method__}\" should be implemented in #{self.class.name} class")
103
+ end
104
+
105
+ def last_limited_sorted_table
106
+ raise NotImplementedError.new("method \"#{__method__}\" should be implemented in #{self.class.name} class")
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Connections
5
+ # Implements pagination by one field with desc order
6
+ module PrimaryKey
7
+ class Desc < Base
8
+ PAGE_COMPARABLE_METHODS = {
9
+ previous: {query: :gt, cursor: :gteq},
10
+ next: {query: :lt, cursor: :lteq}
11
+ }.freeze
12
+
13
+ SLICED_COMPARABLE_METHODS = {
14
+ after: :lt,
15
+ before: :gt
16
+ }.freeze
17
+
18
+ private
19
+
20
+ def first_limited_sorted_table
21
+ arel_table[primary_key].desc
22
+ end
23
+
24
+ def last_limited_sorted_table
25
+ arel_table[primary_key].asc
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -14,9 +14,9 @@ module GraphQL
14
14
  module Stable
15
15
  def self.new(*args, desc: false, keys: nil, **kwargs)
16
16
  if kwargs[:primary_key] || keys.nil?
17
- raise NotImplementedError, "desc connection is not implemented yet" if desc
17
+ cls = desc ? GraphQL::Connections::PrimaryKey::Desc : GraphQL::Connections::PrimaryKey::Asc
18
18
 
19
- return GraphQL::Connections::KeyAsc.new(*args, **kwargs)
19
+ return cls.new(*args, **kwargs)
20
20
  end
21
21
 
22
22
  raise ArgumentError, "keyset for more that 2 keys is not implemented yet" if keys.length > 2
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Paging
5
- VERSION = "1.2.0"
5
+ VERSION = "1.4.0"
6
6
  end
7
7
  end
@@ -11,10 +11,13 @@ end
11
11
  require "graphql/connections/stable"
12
12
 
13
13
  require "graphql/connections/base"
14
- require "graphql/connections/key_asc"
15
14
 
16
15
  require "graphql/connections/keyset/base"
17
16
  require "graphql/connections/keyset/asc"
18
17
  require "graphql/connections/keyset/desc"
19
18
 
19
+ require "graphql/connections/primary_key/base"
20
+ require "graphql/connections/primary_key/asc"
21
+ require "graphql/connections/primary_key/desc"
22
+
20
23
  require "graphql/connections/chewy"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-connections
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Misha Merkushin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-04 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -162,14 +162,14 @@ dependencies:
162
162
  requirements:
163
163
  - - "~>"
164
164
  - !ruby/object:Gem::Version
165
- version: '5.0'
165
+ version: '6.0'
166
166
  type: :development
167
167
  prerelease: false
168
168
  version_requirements: !ruby/object:Gem::Requirement
169
169
  requirements:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
- version: '5.0'
172
+ version: '6.0'
173
173
  - !ruby/object:Gem::Dependency
174
174
  name: standard
175
175
  requirement: !ruby/object:Gem::Requirement
@@ -190,14 +190,14 @@ dependencies:
190
190
  requirements:
191
191
  - - "~>"
192
192
  - !ruby/object:Gem::Version
193
- version: '1.0'
193
+ version: 1.0.0
194
194
  type: :development
195
195
  prerelease: false
196
196
  version_requirements: !ruby/object:Gem::Requirement
197
197
  requirements:
198
198
  - - "~>"
199
199
  - !ruby/object:Gem::Version
200
- version: '1.0'
200
+ version: 1.0.0
201
201
  description:
202
202
  email:
203
203
  - merkushin.m.s@gmail.com
@@ -210,10 +210,12 @@ files:
210
210
  - lib/graphql/connections.rb
211
211
  - lib/graphql/connections/base.rb
212
212
  - lib/graphql/connections/chewy.rb
213
- - lib/graphql/connections/key_asc.rb
214
213
  - lib/graphql/connections/keyset/asc.rb
215
214
  - lib/graphql/connections/keyset/base.rb
216
215
  - lib/graphql/connections/keyset/desc.rb
216
+ - lib/graphql/connections/primary_key/asc.rb
217
+ - lib/graphql/connections/primary_key/base.rb
218
+ - lib/graphql/connections/primary_key/desc.rb
217
219
  - lib/graphql/connections/stable.rb
218
220
  - lib/graphql/connections/version.rb
219
221
  homepage: https://github.com/bibendi/graphql-connections
@@ -229,14 +231,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
229
231
  requirements:
230
232
  - - ">"
231
233
  - !ruby/object:Gem::Version
232
- version: '2.5'
234
+ version: '2.6'
233
235
  required_rubygems_version: !ruby/object:Gem::Requirement
234
236
  requirements:
235
237
  - - ">="
236
238
  - !ruby/object:Gem::Version
237
239
  version: '0'
238
240
  requirements: []
239
- rubygems_version: 3.2.32
241
+ rubygems_version: 3.4.20
240
242
  signing_key:
241
243
  specification_version: 4
242
244
  summary: GraphQL cursor-based stable pagination to work with Active Record relations
@@ -1,69 +0,0 @@
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
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
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
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
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