graphiti 1.2.44 → 1.3.9

Sign up to get free protection for your applications and to get access to all the features.
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