graphiti 1.2.44 → 1.3.9

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +96 -0
  3. data/.standard.yml +4 -4
  4. data/Appraisals +18 -44
  5. data/CHANGELOG.md +1 -0
  6. data/gemfiles/rails_5_2.gemfile +2 -2
  7. data/gemfiles/rails_5_2_graphiti_rails.gemfile +3 -4
  8. data/gemfiles/rails_6_graphiti_rails.gemfile +1 -2
  9. data/gemfiles/{rails_5_1.gemfile → rails_7.gemfile} +2 -2
  10. data/gemfiles/{rails_4.gemfile → rails_7_graphiti_rails.gemfile} +3 -2
  11. data/graphiti.gemspec +5 -5
  12. data/lib/graphiti/adapters/abstract.rb +5 -1
  13. data/lib/graphiti/adapters/active_record.rb +42 -34
  14. data/lib/graphiti/adapters/persistence/associations.rb +13 -15
  15. data/lib/graphiti/delegates/pagination.rb +14 -6
  16. data/lib/graphiti/errors.rb +17 -11
  17. data/lib/graphiti/extensions/temp_id.rb +1 -1
  18. data/lib/graphiti/filter_operators.rb +0 -1
  19. data/lib/graphiti/hash_renderer.rb +40 -2
  20. data/lib/graphiti/query.rb +71 -68
  21. data/lib/graphiti/railtie.rb +4 -4
  22. data/lib/graphiti/renderer.rb +1 -0
  23. data/lib/graphiti/request_validator.rb +1 -1
  24. data/lib/graphiti/resource/configuration.rb +2 -1
  25. data/lib/graphiti/resource/dsl.rb +14 -6
  26. data/lib/graphiti/resource/polymorphism.rb +1 -1
  27. data/lib/graphiti/resource/sideloading.rb +4 -4
  28. data/lib/graphiti/resource.rb +2 -1
  29. data/lib/graphiti/resource_proxy.rb +8 -2
  30. data/lib/graphiti/schema.rb +6 -4
  31. data/lib/graphiti/scope.rb +2 -2
  32. data/lib/graphiti/scoping/paginate.rb +28 -2
  33. data/lib/graphiti/scoping/sort.rb +4 -6
  34. data/lib/graphiti/serializer.rb +19 -1
  35. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +3 -4
  36. data/lib/graphiti/stats/dsl.rb +0 -1
  37. data/lib/graphiti/util/serializer_attributes.rb +6 -0
  38. data/lib/graphiti/util/simple_errors.rb +3 -3
  39. data/lib/graphiti/version.rb +1 -1
  40. data/lib/graphiti.rb +1 -0
  41. metadata +17 -23
  42. data/.travis.yml +0 -94
  43. data/gemfiles/rails_5_0.gemfile +0 -18
  44. data/gemfiles/rails_5_0_graphiti_rails.gemfile +0 -20
  45. data/gemfiles/rails_5_1_graphiti_rails.gemfile +0 -20
@@ -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
@@ -184,12 +192,42 @@ module Graphiti
184
192
  if options[:meta] && !options[:meta].empty?
185
193
  if @graphql
186
194
  if (stats = options[:meta][:stats])
187
- hash[top_level_key][:stats] = stats
195
+ camelized = {}
196
+ stats.each_pair do |key, value|
197
+ camelized[key.to_s.camelize(:lower).to_sym] = value
198
+ end
199
+ hash[top_level_key][:stats] = camelized
188
200
  end
189
201
  else
190
202
  hash.merge!(options.slice(:meta))
191
203
  end
192
204
  end
193
205
  end
206
+
207
+ # NB - this is only for top-level right now
208
+ # The casing here is GQL-specific, we can update later if needed.
209
+ def add_page_info(hash, serializers, top_level_key, options)
210
+ if (fields = options[:fields].try(:[], :page_info))
211
+ info = {}
212
+
213
+ if fields.include?(:has_next_page)
214
+ info[:hasNextPage] = options[:proxy].pagination.has_next_page?
215
+ end
216
+
217
+ if fields.include?(:has_previous_page)
218
+ info[:hasPreviousPage] = options[:proxy].pagination.has_previous_page?
219
+ end
220
+
221
+ if fields.include?(:start_cursor)
222
+ info[:startCursor] = serializers.first.try(:cursor)
223
+ end
224
+
225
+ if fields.include?(:end_cursor)
226
+ info[:endCursor] = serializers.last.try(:cursor)
227
+ end
228
+
229
+ hash[top_level_key][:pageInfo] = info
230
+ end
231
+ end
194
232
  end
195
233
  end
@@ -69,11 +69,9 @@ module Graphiti
69
69
  end
70
70
 
71
71
  def sideload_hash
72
- @sideload_hash = begin
73
- {}.tap do |hash|
74
- sideloads.each_pair do |key, value|
75
- hash[key] = sideloads[key].hash
76
- end
72
+ @sideload_hash = {}.tap do |hash|
73
+ sideloads.each_pair do |key, value|
74
+ hash[key] = sideloads[key].hash
77
75
  end
78
76
  end
79
77
  end
@@ -89,30 +87,28 @@ module Graphiti
89
87
  end
90
88
 
91
89
  def sideloads
92
- @sideloads ||= begin
93
- {}.tap do |hash|
94
- include_hash.each_pair do |key, sub_hash|
95
- sideload = @resource.class.sideload(key)
96
-
97
- if sideload || @resource.remote?
98
- sl_resource = resource_for_sideload(sideload)
99
- query_parents = parents + [self]
100
- sub_hash = sub_hash[:include] if sub_hash.key?(:include)
101
-
102
- # NB: To handle on__<type>--<name>
103
- # A) relationship_name == :positions
104
- # B) key == on__employees.positions
105
- # This way A) ensures sideloads are resolved
106
- # And B) ensures nested filters, sorts etc still work
107
- relationship_name = sideload ? sideload.name : key
108
- hash[relationship_name] = Query.new sl_resource,
109
- @params,
110
- key,
111
- sub_hash,
112
- query_parents, :all
113
- else
114
- handle_missing_sideload(key)
115
- end
90
+ @sideloads ||= {}.tap do |hash|
91
+ include_hash.each_pair do |key, sub_hash|
92
+ sideload = @resource.class.sideload(key)
93
+
94
+ if sideload || @resource.remote?
95
+ sl_resource = resource_for_sideload(sideload)
96
+ query_parents = parents + [self]
97
+ sub_hash = sub_hash[:include] if sub_hash.key?(:include)
98
+
99
+ # NB: To handle on__<type>--<name>
100
+ # A) relationship_name == :positions
101
+ # B) key == on__employees.positions
102
+ # This way A) ensures sideloads are resolved
103
+ # And B) ensures nested filters, sorts etc still work
104
+ relationship_name = sideload ? sideload.name : key
105
+ hash[relationship_name] = Query.new sl_resource,
106
+ @params,
107
+ key,
108
+ sub_hash,
109
+ query_parents, :all
110
+ else
111
+ handle_missing_sideload(key)
116
112
  end
117
113
  end
118
114
  end
@@ -137,27 +133,25 @@ module Graphiti
137
133
  end
138
134
 
139
135
  def filters
140
- @filters ||= begin
141
- {}.tap do |hash|
142
- (@params[:filter] || {}).each_pair do |name, value|
143
- name = name.to_sym
144
-
145
- if legacy_nested?(name)
146
- value.keys.each do |key|
147
- filter_name = key.to_sym
148
- filter_value = value[key]
149
-
150
- if @resource.get_attr!(filter_name, :filterable, request: true)
151
- hash[filter_name] = filter_value
152
- end
136
+ @filters ||= {}.tap do |hash|
137
+ (@params[:filter] || {}).each_pair do |name, value|
138
+ name = name.to_sym
139
+
140
+ if legacy_nested?(name)
141
+ value.keys.each do |key|
142
+ filter_name = key.to_sym
143
+ filter_value = value[key]
144
+
145
+ if @resource.get_attr!(filter_name, :filterable, request: true)
146
+ hash[filter_name] = filter_value
153
147
  end
154
- elsif nested?(name)
155
- name = name.to_s.split(".").last.to_sym
156
- validate!(name, :filterable)
157
- hash[name] = value
158
- elsif top_level? && validate!(name, :filterable)
159
- hash[name] = value
160
148
  end
149
+ elsif nested?(name)
150
+ name = name.to_s.split(".").last.to_sym
151
+ validate!(name, :filterable)
152
+ hash[name] = value
153
+ elsif top_level? && validate!(name, :filterable)
154
+ hash[name] = value
161
155
  end
162
156
  end
163
157
  end
@@ -186,18 +180,17 @@ module Graphiti
186
180
  end
187
181
 
188
182
  def pagination
189
- @pagination ||= begin
190
- {}.tap do |hash|
191
- (@params[:page] || {}).each_pair do |name, value|
192
- if legacy_nested?(name)
193
- value.each_pair do |k, v|
194
- hash[k.to_sym] = v.to_i
195
- end
196
- elsif nested?(name)
197
- hash[name.to_s.split(".").last.to_sym] = value
198
- elsif top_level? && [:number, :size, :offset].include?(name.to_sym)
199
- hash[name.to_sym] = value.to_i
183
+ @pagination ||= {}.tap do |hash|
184
+ (@params[:page] || {}).each_pair do |name, value|
185
+ if legacy_nested?(name)
186
+ value.each_pair do |k, v|
187
+ hash[k.to_sym] = cast_page_param(k.to_sym, v)
200
188
  end
189
+ elsif nested?(name)
190
+ param_name = name.to_s.split(".").last.to_sym
191
+ hash[param_name] = cast_page_param(param_name, value)
192
+ elsif top_level? && Scoping::Paginate::PARAMS.include?(name.to_sym)
193
+ hash[name.to_sym] = cast_page_param(name.to_sym, value)
201
194
  end
202
195
  end
203
196
  end
@@ -220,15 +213,13 @@ module Graphiti
220
213
  end
221
214
 
222
215
  def stats
223
- @stats ||= begin
224
- {}.tap do |hash|
225
- (@params[:stats] || {}).each_pair do |k, v|
226
- if legacy_nested?(k)
227
- raise NotImplementedError.new("Association statistics are not currently supported")
228
- elsif top_level?
229
- v = v.split(",") if v.is_a?(String)
230
- hash[k.to_sym] = Array(v).flatten.map(&:to_sym)
231
- end
216
+ @stats ||= {}.tap do |hash|
217
+ (@params[:stats] || {}).each_pair do |k, v|
218
+ if legacy_nested?(k)
219
+ raise NotImplementedError.new("Association statistics are not currently supported")
220
+ elsif top_level?
221
+ v = v.split(",") if v.is_a?(String)
222
+ hash[k.to_sym] = Array(v).flatten.map(&:to_sym)
232
223
  end
233
224
  end
234
225
  end
@@ -240,6 +231,18 @@ module Graphiti
240
231
 
241
232
  private
242
233
 
234
+ def cast_page_param(name, value)
235
+ if [:before, :after].include?(name)
236
+ decode_cursor(value)
237
+ else
238
+ value.to_i
239
+ end
240
+ end
241
+
242
+ def decode_cursor(cursor)
243
+ JSON.parse(Base64.decode64(cursor)).symbolize_keys
244
+ end
245
+
243
246
  # Try to find on this resource
244
247
  # If not there, follow the legacy logic of scalling all other
245
248
  # resource names/types
@@ -110,10 +110,10 @@ module Graphiti
110
110
  end
111
111
 
112
112
  route = begin
113
- ::Rails.application.routes.recognize_path(path, method: method)
114
- rescue
115
- nil
116
- end
113
+ ::Rails.application.routes.recognize_path(path, method: method)
114
+ rescue
115
+ nil
116
+ end
117
117
  "#{route[:controller]}_controller".classify.safe_constantize if route
118
118
  }
119
119
  end
@@ -66,6 +66,7 @@ module Graphiti
66
66
  options[:meta] ||= proxy.meta
67
67
  options[:meta][:stats] = proxy.stats unless proxy.stats.empty?
68
68
  options[:meta][:debug] = Debugger.to_a if debug_json?
69
+ options[:proxy] = proxy
69
70
 
70
71
  renderer.render(records, options)
71
72
  end
@@ -13,7 +13,7 @@ module Graphiti
13
13
  class ValidatorFactory
14
14
  def self.create(root_resource, raw_params, action)
15
15
  case action
16
- when :update then
16
+ when :update
17
17
  RequestValidators::UpdateValidator
18
18
  else
19
19
  RequestValidators::Validator
@@ -92,7 +92,8 @@ module Graphiti
92
92
  :relationships_writable_by_default,
93
93
  :filters_accept_nil_by_default,
94
94
  :filters_deny_empty_by_default,
95
- :graphql_entrypoint
95
+ :graphql_entrypoint,
96
+ :cursor_paginatable
96
97
 
97
98
  class << self
98
99
  prepend Overrides
@@ -9,7 +9,11 @@ module Graphiti
9
9
  opts = args.extract_options!
10
10
  type_override = args[0]
11
11
 
12
- if (att = get_attr(name, :filterable, raise_error: :only_unsupported))
12
+ if (att = (attributes[name] || extra_attributes[name]))
13
+ # We're opting in to filtering, so force this
14
+ # UNLESS the filter is guarded at the attribute level
15
+ att[:filterable] = true if att[:filterable] == false
16
+
13
17
  aliases = [name, opts[:aliases]].flatten.compact
14
18
  operators = FilterOperators.build(self, att[:type], opts, &blk)
15
19
 
@@ -23,6 +27,8 @@ module Graphiti
23
27
  end
24
28
 
25
29
  required = att[:filterable] == :required || !!opts[:required]
30
+ schema = !!opts[:via_attribute_dsl] ? att[:schema] : opts[:schema] != false
31
+
26
32
  config[:filters][name.to_sym] = {
27
33
  aliases: aliases,
28
34
  name: name.to_sym,
@@ -32,6 +38,7 @@ module Graphiti
32
38
  single: !!opts[:single],
33
39
  dependencies: opts[:dependent],
34
40
  required: required,
41
+ schema: schema,
35
42
  operators: operators.to_hash,
36
43
  allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default),
37
44
  deny_empty: opts.fetch(:deny_empty, filters_deny_empty_by_default)
@@ -56,7 +63,7 @@ module Graphiti
56
63
  end
57
64
 
58
65
  def sort_all(&blk)
59
- if block_given?
66
+ if blk
60
67
  config[:_sort_all] = blk
61
68
  else
62
69
  config[:_sort_all]
@@ -130,7 +137,7 @@ module Graphiti
130
137
  options[:sortable] ? sort(name) : config[:sorts].delete(name)
131
138
 
132
139
  if options[:filterable]
133
- filter(name, allow: options[:allow])
140
+ filter(name, allow: options[:allow], via_attribute_dsl: true)
134
141
  else
135
142
  config[:filters].delete(name)
136
143
  end
@@ -144,7 +151,8 @@ module Graphiti
144
151
  readable: true,
145
152
  writable: false,
146
153
  sortable: false,
147
- filterable: false
154
+ filterable: false,
155
+ schema: true
148
156
  }
149
157
  options = defaults.merge(options)
150
158
  attribute_option(options, :readable)
@@ -181,9 +189,9 @@ module Graphiti
181
189
  def attribute_option(options, name, exclusive = false)
182
190
  if options[name] != false
183
191
  default = if (only = options[:only]) && !exclusive
184
- Array(only).include?(name) ? true : false
192
+ Array(only).include?(name)
185
193
  elsif (except = options[:except]) && !exclusive
186
- Array(except).include?(name) ? false : true
194
+ !Array(except).include?(name)
187
195
  else
188
196
  send(:"attributes_#{name}_by_default")
189
197
  end
@@ -72,7 +72,7 @@ module Graphiti
72
72
  end
73
73
 
74
74
  def resource_for_model(model)
75
- resource = children.find { |c| model.class == c.model } ||
75
+ resource = children.find { |c| model.instance_of?(c.model) } ||
76
76
  children.find { |c| model.is_a?(c.model) }
77
77
  if resource.nil?
78
78
  raise Errors::PolymorphicResourceChildNotFound.new(self, model: model)
@@ -68,10 +68,10 @@ module Graphiti
68
68
  model_ref = model
69
69
  has_many name, opts do
70
70
  params do |hash|
71
- hash[:filter][:"#{as}_type"] = model_ref.name
71
+ hash[:filter][:"#{as}_type"] = { eql: model_ref.name }
72
72
  end
73
73
 
74
- instance_eval(&blk) if block_given?
74
+ instance_eval(&blk) if blk
75
75
  end
76
76
  end
77
77
 
@@ -82,10 +82,10 @@ module Graphiti
82
82
  model_ref = model
83
83
  has_one name, opts do
84
84
  params do |hash|
85
- hash[:filter][:"#{as}_type"] = model_ref.name
85
+ hash[:filter][:"#{as}_type"] = { eql: model_ref.name }
86
86
  end
87
87
 
88
- instance_eval(&blk) if block_given?
88
+ instance_eval(&blk) if blk
89
89
  end
90
90
  end
91
91
 
@@ -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
 
@@ -73,7 +73,7 @@ module Graphiti
73
73
  records
74
74
  end
75
75
  end
76
- alias to_a data
76
+ alias_method :to_a, :data
77
77
 
78
78
  def meta
79
79
  @meta ||= data.respond_to?(:meta) ? data.meta : {}
@@ -85,9 +85,15 @@ module Graphiti
85
85
 
86
86
  def stats
87
87
  @stats ||= if @query.hash[:stats]
88
+ scope = @scope.unpaginated_object
89
+ if resource.adapter.can_group?
90
+ if (group = @query.hash[:stats].delete(:group_by))
91
+ scope = resource.adapter.group(scope, group[0])
92
+ end
93
+ end
88
94
  payload = Stats::Payload.new @resource,
89
95
  @query,
90
- @scope.unpaginated_object,
96
+ scope,
91
97
  data
92
98
  payload.generate
93
99
  else
@@ -153,6 +153,8 @@ module Graphiti
153
153
  def extra_attributes(resource)
154
154
  {}.tap do |attrs|
155
155
  resource.extra_attributes.each_pair do |name, config|
156
+ next unless config[:schema]
157
+
156
158
  attrs[name] = {
157
159
  type: config[:type].to_s,
158
160
  readable: flag(config[:readable]),
@@ -181,11 +183,11 @@ module Graphiti
181
183
  def sorts(resource)
182
184
  {}.tap do |s|
183
185
  resource.sorts.each_pair do |name, sort|
184
- next unless resource.attributes[name][:schema]
186
+ attr = resource.all_attributes[name]
187
+ next unless attr[:schema]
185
188
 
186
189
  config = {}
187
190
  config[:only] = sort[:only] if sort[:only]
188
- attr = resource.attributes[name]
189
191
  if attr[:sortable].is_a?(Symbol)
190
192
  config[:guard] = true
191
193
  end
@@ -197,7 +199,7 @@ module Graphiti
197
199
  def filters(resource)
198
200
  {}.tap do |f|
199
201
  resource.filters.each_pair do |name, filter|
200
- next unless resource.attributes[name][:schema]
202
+ next unless resource.filters[name][:schema]
201
203
 
202
204
  config = {
203
205
  type: filter[:type].to_s,
@@ -209,7 +211,7 @@ module Graphiti
209
211
  config[:deny] = filter[:deny].map(&:to_s) if filter[:deny]
210
212
  config[:dependencies] = filter[:dependencies].map(&:to_s) if filter[:dependencies]
211
213
 
212
- attr = resource.attributes[name]
214
+ attr = resource.all_attributes[name]
213
215
  if attr[:filterable].is_a?(Symbol)
214
216
  if attr[:filterable] == :required
215
217
  config[:required] = true
@@ -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.each do |r|
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
 
@@ -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
@@ -56,7 +57,7 @@ module Graphiti
56
57
  private
57
58
 
58
59
  def requested?
59
- ![page_param[:size], page_param[:number]].all?(&:nil?)
60
+ !PARAMS.map { |p| page_param[p] }.all?(&:nil?)
60
61
  end
61
62
 
62
63
  def page_param
@@ -64,9 +65,34 @@ module Graphiti
64
65
  end
65
66
 
66
67
  def offset
68
+ offset = nil
69
+
67
70
  if (value = page_param[:offset])
68
- value.to_i
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]
69
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]
70
96
  end
71
97
 
72
98
  def number
@@ -59,12 +59,10 @@ module Graphiti
59
59
  end
60
60
 
61
61
  def sort_param
62
- @sort_param ||= begin
63
- if query_hash[:sort].blank?
64
- resource.default_sort || []
65
- else
66
- normalize(query_hash[:sort])
67
- end
62
+ @sort_param ||= if query_hash[:sort].blank?
63
+ resource.default_sort || []
64
+ else
65
+ normalize(query_hash[:sort])
68
66
  end
69
67
  end
70
68
 
@@ -25,8 +25,9 @@ module Graphiti
25
25
 
26
26
  # See #requested_relationships
27
27
  def self.relationship(name, options = {}, &block)
28
+ prev = Util::Hash.deep_dup(field_condition_blocks)
28
29
  super
29
- field_condition_blocks.delete(name)
30
+ self.field_condition_blocks = prev
30
31
  _register_condition(relationship_condition_blocks, name, options)
31
32
  end
32
33
 
@@ -45,6 +46,23 @@ module Graphiti
45
46
  end
46
47
  end
47
48
 
49
+ def cursor
50
+ starting_offset = 0
51
+ page_param = @proxy.query.pagination
52
+ if (page_number = page_param[:number])
53
+ page_size = page_param[:size] || @resource.default_page_size
54
+ starting_offset = (page_number - 1) * page_size
55
+ end
56
+
57
+ if (cursor = page_param[:after])
58
+ starting_offset = cursor[:offset]
59
+ end
60
+
61
+ current_offset = @object.instance_variable_get(:@__graphiti_index)
62
+ offset = starting_offset + current_offset + 1 # (+ 1 b/c o-base index)
63
+ Base64.encode64({offset: offset}.to_json).chomp
64
+ end
65
+
48
66
  def as_jsonapi(kwargs = {})
49
67
  super(**kwargs).tap do |hash|
50
68
  strip_relationships!(hash) if strip_relationships?
@@ -7,7 +7,6 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
7
7
  @calls = []
8
8
  end
9
9
 
10
- # rubocop: disable Style/MethodMissingSuper
11
10
  def method_missing(name, *args, &blk)
12
11
  @calls << [name, args, blk]
13
12
  end
@@ -56,9 +55,9 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
56
55
  args = call[1]
57
56
  opts = args.extract_options!
58
57
  opts.merge! as: sideload.name,
59
- parent: sideload,
60
- group_name: group.name,
61
- polymorphic_child: true
58
+ parent: sideload,
59
+ group_name: group.name,
60
+ polymorphic_child: true
62
61
  unless sideload.resource.class.abstract_class?
63
62
  opts[:foreign_key] ||= sideload.foreign_key
64
63
  opts[:primary_key] ||= sideload.primary_key
@@ -46,7 +46,6 @@ module Graphiti
46
46
  #
47
47
  # ...will hit +method_missing+ and store the proc for future reference.
48
48
  # @api private
49
- # rubocop: disable Style/MethodMissingSuper
50
49
  def method_missing(meth, *args, &blk)
51
50
  @calculations[meth] = blk
52
51
  end
@@ -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