graphiti 1.2.41 → 1.3.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/lib/graphiti/adapters/abstract.rb +3 -2
- data/lib/graphiti/adapters/active_record.rb +5 -2
- data/lib/graphiti/adapters/null.rb +1 -1
- data/lib/graphiti/delegates/pagination.rb +30 -10
- data/lib/graphiti/errors.rb +6 -0
- data/lib/graphiti/hash_renderer.rb +35 -1
- data/lib/graphiti/query.rb +20 -5
- data/lib/graphiti/renderer.rb +1 -0
- data/lib/graphiti/resource.rb +2 -1
- data/lib/graphiti/resource/configuration.rb +2 -1
- data/lib/graphiti/scope.rb +2 -2
- data/lib/graphiti/scoping/filter.rb +12 -8
- data/lib/graphiti/scoping/paginate.rb +47 -3
- data/lib/graphiti/serializer.rb +17 -0
- data/lib/graphiti/util/serializer_attributes.rb +6 -0
- data/lib/graphiti/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c694cce2123c7ba03ae53b53b61b9aaa5c353681e28a93722bc73569845ea2e2
|
4
|
+
data.tar.gz: 2305bdf6566d1bcba126dbe893dd64f6f011367151e0a283185fba62781d0d02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d9d20b0b87720079461b0ce245970da308362616cb4c21c4232fb85c585a62694f3bdb874db3cf8d80e94e02ff3078c119e0a0a46e95dbdb94e439dbf5a1af8
|
7
|
+
data.tar.gz: 9cf83060d95a7789351f3f0979ccf46012238975248159d8a5fccd0e457b512cfc127b3a4295c46f668b80717dfec2152fea025d00a82a1ffcdd42edde9e5c7f
|
@@ -244,14 +244,15 @@ module Graphiti
|
|
244
244
|
# @param scope The scope object we are chaining
|
245
245
|
# @param [Integer] current_page The current page number
|
246
246
|
# @param [Integer] per_page The number of results per page
|
247
|
+
# @param [Integer] offset The offset to start from
|
247
248
|
# @return the scope
|
248
249
|
#
|
249
250
|
# @example ActiveRecord default
|
250
251
|
# # via kaminari gem
|
251
|
-
# def paginate(scope, current_page, per_page)
|
252
|
+
# def paginate(scope, current_page, per_page, offset)
|
252
253
|
# scope.page(current_page).per(per_page)
|
253
254
|
# end
|
254
|
-
def paginate(scope, current_page, per_page)
|
255
|
+
def paginate(scope, current_page, per_page, offset)
|
255
256
|
raise "you must override #paginate in an adapter subclass"
|
256
257
|
end
|
257
258
|
|
@@ -184,8 +184,11 @@ module Graphiti
|
|
184
184
|
end
|
185
185
|
|
186
186
|
# (see Adapters::Abstract#paginate)
|
187
|
-
def paginate(scope, current_page, per_page)
|
188
|
-
scope.page(current_page)
|
187
|
+
def paginate(scope, current_page, per_page, offset)
|
188
|
+
scope = scope.page(current_page) if current_page
|
189
|
+
scope = scope.per(per_page) if per_page
|
190
|
+
scope = scope.padding(offset) if offset
|
191
|
+
scope
|
189
192
|
end
|
190
193
|
|
191
194
|
# (see Adapters::Abstract#count)
|
@@ -14,11 +14,19 @@ module Graphiti
|
|
14
14
|
links[:self] = pagination_link(current_page)
|
15
15
|
links[:first] = pagination_link(1)
|
16
16
|
links[:last] = pagination_link(last_page)
|
17
|
-
links[:prev] = pagination_link(current_page - 1)
|
18
|
-
links[:next] = pagination_link(current_page + 1)
|
17
|
+
links[:prev] = pagination_link(current_page - 1) if has_previous_page?
|
18
|
+
links[:next] = pagination_link(current_page + 1) if has_next_page?
|
19
19
|
end.select { |k, v| !v.nil? }
|
20
20
|
end
|
21
21
|
|
22
|
+
def has_next_page?
|
23
|
+
current_page != last_page && last_page.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_previous_page?
|
27
|
+
current_page != 1
|
28
|
+
end
|
29
|
+
|
22
30
|
private
|
23
31
|
|
24
32
|
def pagination_params
|
@@ -30,13 +38,14 @@ module Graphiti
|
|
30
38
|
|
31
39
|
uri = URI(@proxy.resource.endpoint[:url].to_s)
|
32
40
|
|
41
|
+
page_params = {
|
42
|
+
number: page,
|
43
|
+
size: page_size
|
44
|
+
}
|
45
|
+
page_params[:offset] = offset if offset
|
46
|
+
|
33
47
|
# Overwrite the pagination query params with the desired page
|
34
|
-
uri.query = pagination_params.merge(
|
35
|
-
page: {
|
36
|
-
number: page,
|
37
|
-
size: page_size
|
38
|
-
}
|
39
|
-
}).to_query
|
48
|
+
uri.query = pagination_params.merge(page: page_params).to_query
|
40
49
|
uri.to_s
|
41
50
|
end
|
42
51
|
|
@@ -46,8 +55,11 @@ module Graphiti
|
|
46
55
|
elsif page_size == 0 || item_count == 0
|
47
56
|
return nil
|
48
57
|
end
|
49
|
-
|
50
|
-
|
58
|
+
|
59
|
+
count = item_count
|
60
|
+
count = item_count - offset if offset
|
61
|
+
@last_page = (count / page_size)
|
62
|
+
@last_page += 1 if count % page_size > 0
|
51
63
|
@last_page
|
52
64
|
end
|
53
65
|
|
@@ -82,6 +94,14 @@ module Graphiti
|
|
82
94
|
@current_page ||= (page_param[:number] || 1).to_i
|
83
95
|
end
|
84
96
|
|
97
|
+
def offset
|
98
|
+
@offset ||= begin
|
99
|
+
if (value = page_param[:offset])
|
100
|
+
value.to_i
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
85
105
|
def page_size
|
86
106
|
@page_size ||= (page_param[:size] ||
|
87
107
|
@proxy.resource.default_page_size ||
|
data/lib/graphiti/errors.rb
CHANGED
@@ -733,6 +733,12 @@ module Graphiti
|
|
733
733
|
end
|
734
734
|
end
|
735
735
|
|
736
|
+
class UnsupportedBeforeCursor < Base
|
737
|
+
def message
|
738
|
+
"Passing in page[before] and page[number] is not supported. Please create an issue if you need it!"
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
736
742
|
class InvalidInclude < Base
|
737
743
|
def initialize(resource, relationship)
|
738
744
|
@resource = resource
|
@@ -101,6 +101,10 @@ module Graphiti
|
|
101
101
|
hash[:_type] = jsonapi_type.to_s
|
102
102
|
end
|
103
103
|
|
104
|
+
if (fields_list || []).include?(:_cursor)
|
105
|
+
hash[:_cursor] = cursor
|
106
|
+
end
|
107
|
+
|
104
108
|
if (fields_list || []).include?(:__typename)
|
105
109
|
resource_class = @resource.class
|
106
110
|
if polymorphic_subclass?
|
@@ -142,6 +146,10 @@ module Graphiti
|
|
142
146
|
nodes = get_nodes(serializers, opts)
|
143
147
|
add_nodes(hash, top_level_key, options, nodes, @graphql)
|
144
148
|
add_stats(hash, top_level_key, options, @graphql)
|
149
|
+
if @graphql
|
150
|
+
add_page_info(hash, serializers, top_level_key, options)
|
151
|
+
end
|
152
|
+
|
145
153
|
hash
|
146
154
|
end
|
147
155
|
|
@@ -160,7 +168,7 @@ module Graphiti
|
|
160
168
|
|
161
169
|
def get_nodes(serializers, opts)
|
162
170
|
if serializers.is_a?(Array)
|
163
|
-
serializers.map do |s|
|
171
|
+
serializers.each_with_index.map do |s, index|
|
164
172
|
s.to_hash(**opts)
|
165
173
|
end
|
166
174
|
else
|
@@ -191,5 +199,31 @@ module Graphiti
|
|
191
199
|
end
|
192
200
|
end
|
193
201
|
end
|
202
|
+
|
203
|
+
# NB - this is only for top-level right now
|
204
|
+
# The casing here is GQL-specific, we can update later if needed.
|
205
|
+
def add_page_info(hash, serializers, top_level_key, options)
|
206
|
+
if (fields = options[:fields].try(:[], :page_info))
|
207
|
+
info = {}
|
208
|
+
|
209
|
+
if fields.include?(:has_next_page)
|
210
|
+
info[:hasNextPage] = options[:proxy].pagination.has_next_page?
|
211
|
+
end
|
212
|
+
|
213
|
+
if fields.include?(:has_previous_page)
|
214
|
+
info[:hasPreviousPage] = options[:proxy].pagination.has_previous_page?
|
215
|
+
end
|
216
|
+
|
217
|
+
if fields.include?(:start_cursor)
|
218
|
+
info[:startCursor] = serializers.first.cursor
|
219
|
+
end
|
220
|
+
|
221
|
+
if fields.include?(:end_cursor)
|
222
|
+
info[:endCursor] = serializers.last.cursor
|
223
|
+
end
|
224
|
+
|
225
|
+
hash[top_level_key][:pageInfo] = info
|
226
|
+
end
|
227
|
+
end
|
194
228
|
end
|
195
229
|
end
|
data/lib/graphiti/query.rb
CHANGED
@@ -32,7 +32,9 @@ module Graphiti
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def pagination_links?
|
35
|
-
if
|
35
|
+
if action == :find
|
36
|
+
false
|
37
|
+
elsif Graphiti.config.pagination_links_on_demand
|
36
38
|
[true, "true"].include?(@params[:pagination_links])
|
37
39
|
else
|
38
40
|
Graphiti.config.pagination_links
|
@@ -189,12 +191,13 @@ module Graphiti
|
|
189
191
|
(@params[:page] || {}).each_pair do |name, value|
|
190
192
|
if legacy_nested?(name)
|
191
193
|
value.each_pair do |k, v|
|
192
|
-
hash[k.to_sym] = v
|
194
|
+
hash[k.to_sym] = cast_page_param(k.to_sym, v)
|
193
195
|
end
|
194
196
|
elsif nested?(name)
|
195
|
-
|
196
|
-
|
197
|
-
|
197
|
+
param_name = name.to_s.split(".").last.to_sym
|
198
|
+
hash[param_name] = cast_page_param(param_name, value)
|
199
|
+
elsif top_level? && Scoping::Paginate::PARAMS.include?(name.to_sym)
|
200
|
+
hash[name.to_sym] = cast_page_param(name.to_sym, value)
|
198
201
|
end
|
199
202
|
end
|
200
203
|
end
|
@@ -238,6 +241,18 @@ module Graphiti
|
|
238
241
|
|
239
242
|
private
|
240
243
|
|
244
|
+
def cast_page_param(name, value)
|
245
|
+
if [:before, :after].include?(name)
|
246
|
+
decode_cursor(value)
|
247
|
+
else
|
248
|
+
value.to_i
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def decode_cursor(cursor)
|
253
|
+
JSON.parse(Base64.decode64(cursor)).symbolize_keys
|
254
|
+
end
|
255
|
+
|
241
256
|
# Try to find on this resource
|
242
257
|
# If not there, follow the legacy logic of scalling all other
|
243
258
|
# resource names/types
|
data/lib/graphiti/renderer.rb
CHANGED
data/lib/graphiti/resource.rb
CHANGED
@@ -28,11 +28,12 @@ module Graphiti
|
|
28
28
|
serializer
|
29
29
|
end
|
30
30
|
|
31
|
-
def decorate_record(record)
|
31
|
+
def decorate_record(record, index = nil)
|
32
32
|
unless record.instance_variable_get(:@__graphiti_serializer)
|
33
33
|
serializer = serializer_for(record)
|
34
34
|
record.instance_variable_set(:@__graphiti_serializer, serializer)
|
35
35
|
record.instance_variable_set(:@__graphiti_resource, self)
|
36
|
+
record.instance_variable_set(:@__graphiti_index, index) if index
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
data/lib/graphiti/scope.rb
CHANGED
@@ -85,8 +85,8 @@ module Graphiti
|
|
85
85
|
# Used to ensure the resource's serializer is used
|
86
86
|
# Not one derived through the usual jsonapi-rb logic
|
87
87
|
def assign_serializer(records)
|
88
|
-
records.
|
89
|
-
@resource.decorate_record(r)
|
88
|
+
records.each_with_index do |r, index|
|
89
|
+
@resource.decorate_record(r, index)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -171,14 +171,18 @@ module Graphiti
|
|
171
171
|
type = Graphiti::Types[filter[:type]]
|
172
172
|
array_or_string = [:string, :array].include?(type[:canonical_name])
|
173
173
|
if (arr = value.scan(/\[.*?\]/)).present? && array_or_string
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
174
|
+
begin
|
175
|
+
value = arr.map { |json|
|
176
|
+
begin
|
177
|
+
JSON.parse(json)
|
178
|
+
rescue
|
179
|
+
raise Errors::InvalidJSONArray.new(resource, value)
|
180
|
+
end
|
181
|
+
}
|
182
|
+
value = value[0] if value.length == 1
|
183
|
+
rescue Errors::InvalidJSONArray => e
|
184
|
+
raise(e) if type[:canonical_name] == :array
|
185
|
+
end
|
182
186
|
else
|
183
187
|
value = parse_string_arrays(value, !!filter[:single])
|
184
188
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class Scoping::Paginate < Scoping::Base
|
3
3
|
DEFAULT_PAGE_SIZE = 20
|
4
|
+
PARAMS = [:number, :size, :offset, :before, :after]
|
4
5
|
|
5
6
|
def apply
|
6
7
|
if size > resource.max_page_size
|
@@ -33,24 +34,67 @@ module Graphiti
|
|
33
34
|
|
34
35
|
# Apply default pagination proc via the Resource adapter
|
35
36
|
def apply_standard_scope
|
36
|
-
resource.adapter.paginate
|
37
|
+
meth = resource.adapter.method(:paginate)
|
38
|
+
|
39
|
+
if meth.arity == 4 # backwards-compat
|
40
|
+
resource.adapter.paginate(@scope, number, size, offset)
|
41
|
+
else
|
42
|
+
resource.adapter.paginate(@scope, number, size)
|
43
|
+
end
|
37
44
|
end
|
38
45
|
|
39
46
|
# Apply the custom pagination proc
|
40
47
|
def apply_custom_scope
|
41
|
-
resource.instance_exec
|
48
|
+
resource.instance_exec \
|
49
|
+
@scope,
|
50
|
+
number,
|
51
|
+
size,
|
52
|
+
resource.context,
|
53
|
+
offset,
|
54
|
+
&custom_scope
|
42
55
|
end
|
43
56
|
|
44
57
|
private
|
45
58
|
|
46
59
|
def requested?
|
47
|
-
!
|
60
|
+
!PARAMS.map { |p| page_param[p] }.all?(&:nil?)
|
48
61
|
end
|
49
62
|
|
50
63
|
def page_param
|
51
64
|
@page_param ||= (query_hash[:page] || {})
|
52
65
|
end
|
53
66
|
|
67
|
+
def offset
|
68
|
+
offset = nil
|
69
|
+
|
70
|
+
if (value = page_param[:offset])
|
71
|
+
offset = value.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
if before_cursor&.key?(:offset)
|
75
|
+
if page_param.key?(:number)
|
76
|
+
raise Errors::UnsupportedBeforeCursor
|
77
|
+
end
|
78
|
+
|
79
|
+
offset = before_cursor[:offset] - (size * number) - 1
|
80
|
+
offset = 0 if offset.negative?
|
81
|
+
end
|
82
|
+
|
83
|
+
if after_cursor&.key?(:offset)
|
84
|
+
offset = after_cursor[:offset]
|
85
|
+
end
|
86
|
+
|
87
|
+
offset
|
88
|
+
end
|
89
|
+
|
90
|
+
def after_cursor
|
91
|
+
page_param[:after]
|
92
|
+
end
|
93
|
+
|
94
|
+
def before_cursor
|
95
|
+
page_param[:before]
|
96
|
+
end
|
97
|
+
|
54
98
|
def number
|
55
99
|
(page_param[:number] || 1).to_i
|
56
100
|
end
|
data/lib/graphiti/serializer.rb
CHANGED
@@ -45,6 +45,23 @@ module Graphiti
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
def cursor
|
49
|
+
starting_offset = 0
|
50
|
+
page_param = @proxy.query.pagination
|
51
|
+
if (page_number = page_param[:number])
|
52
|
+
page_size = page_param[:size] || @resource.default_page_size
|
53
|
+
starting_offset = (page_number - 1) * page_size
|
54
|
+
end
|
55
|
+
|
56
|
+
if (cursor = page_param[:after])
|
57
|
+
starting_offset = cursor[:offset]
|
58
|
+
end
|
59
|
+
|
60
|
+
current_offset = @object.instance_variable_get(:@__graphiti_index)
|
61
|
+
offset = starting_offset + current_offset + 1 # (+ 1 b/c o-base index)
|
62
|
+
Base64.encode64({offset: offset}.to_json).chomp
|
63
|
+
end
|
64
|
+
|
48
65
|
def as_jsonapi(kwargs = {})
|
49
66
|
super(**kwargs).tap do |hash|
|
50
67
|
strip_relationships!(hash) if strip_relationships?
|
@@ -28,6 +28,12 @@ module Graphiti
|
|
28
28
|
|
29
29
|
existing = @serializer.send(applied_method)
|
30
30
|
@serializer.send(:"#{applied_method}=", [@name] | existing)
|
31
|
+
|
32
|
+
@serializer.meta do
|
33
|
+
if !!@resource.try(:cursor_paginatable?) && !Graphiti.context[:graphql]
|
34
|
+
{cursor: cursor}
|
35
|
+
end
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
private
|
data/lib/graphiti/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphiti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jsonapi-serializable
|
@@ -361,7 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
361
361
|
- !ruby/object:Gem::Version
|
362
362
|
version: '0'
|
363
363
|
requirements: []
|
364
|
-
rubygems_version: 3.
|
364
|
+
rubygems_version: 3.1.4
|
365
365
|
signing_key:
|
366
366
|
specification_version: 4
|
367
367
|
summary: Easily build jsonapi.org-compatible APIs
|