graphiti 1.2.43 → 1.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6020ab7e3340dafb7ddad630e79456d6e50b581aaa3cd455069ad5a25860fbb5
4
- data.tar.gz: da8286216aab03780ec61427289aa6fecfb82c6dc2ff374de5cbb4bd223ddbcd
3
+ metadata.gz: e07d725924af0735b8871f3f03b3164a0da894a3b23162fc8dc4e1fee94dcaf9
4
+ data.tar.gz: b5ea126cb269428beb5f9d52383f4dfc643133871a1fd4e7f72eff45e08e69cc
5
5
  SHA512:
6
- metadata.gz: 5c6dc240dbad87f9900412ac9afdfa32a20963e79827fcdb4bba94c2f7cac159057e80410f797da42b243dbca14d3753a60a6bfb7df404b16b01d8bad2c3c1d6
7
- data.tar.gz: f12629255a14d042b311101110a47fbfe4a099a618e40c2af020f33b08d50bf00741d7f78c81cea0031748828774871d0d954be79a7f6c681a33bae33729f166
6
+ metadata.gz: 39317b810fbf79762dfce8350c620ade336b6a6b8f94e643e02a70cbba6e58d498a3a518672ea70dc906b3e269a9ae6c35bb4c309c15253e65c7bd7190c9d2fc
7
+ data.tar.gz: eb010670119a2361e2f468a8201e8f2ceef564a63e2af14ca3ca2565d3c461e65f0026147fbf25b1abfc5a093a6d9111fc00772d01830f9fbec206e47d066854
data/CHANGELOG.md CHANGED
@@ -12,6 +12,7 @@ Features:
12
12
  Fixes:
13
13
  - [282] Support model names including "Resource"
14
14
  - [313](https://github.com/graphiti-api/graphiti/pull/313) Sort remote resources in schema generation
15
+ - [374](https://github.com/graphiti-api/graphiti/pull/374) Trim leading spaces from error messages
15
16
 
16
17
  ## 1.1.0
17
18
 
@@ -14,11 +14,21 @@ 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) unless current_page == 1
18
- links[:next] = pagination_link(current_page + 1) unless current_page == last_page
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
+ !!pagination_params.try(:[], :page).try(:[], :after) ||
29
+ !!pagination_params.try(:[], :page).try(:[], :offset)
30
+ end
31
+
22
32
  private
23
33
 
24
34
  def pagination_params
@@ -35,7 +35,7 @@ module Graphiti
35
35
  end
36
36
 
37
37
  def message
38
- <<-MSG
38
+ <<~MSG
39
39
  The adapter #{@adapter.class} does not implement method '#{@method}', which was requested for attribute '#{@attribute}'. Add this method to your adapter to support this filter operator.
40
40
  MSG
41
41
  end
@@ -49,7 +49,7 @@ module Graphiti
49
49
  end
50
50
 
51
51
  def message
52
- <<-MSG
52
+ <<~MSG
53
53
  #{@parent_resource_class} sideload :#{@name} - #{@message}
54
54
  MSG
55
55
  end
@@ -78,7 +78,7 @@ module Graphiti
78
78
  end
79
79
 
80
80
  def message
81
- <<-MSG
81
+ <<~MSG
82
82
  #{@resource_class}: Tried to pass block to .#{@method_name}, which only accepts a method name.
83
83
  MSG
84
84
  end
@@ -90,7 +90,7 @@ module Graphiti
90
90
  end
91
91
 
92
92
  def message
93
- <<-MSG
93
+ <<~MSG
94
94
  #{@resource_class}: Tried to perform write operation. Writes are not supported for remote resources - hit the endpoint directly.
95
95
  MSG
96
96
  end
@@ -105,7 +105,7 @@ module Graphiti
105
105
  end
106
106
 
107
107
  def message
108
- <<-MSG
108
+ <<~MSG
109
109
  #{@resource.class}: Tried to filter #{@filter_name.inspect} on operator #{@operator.inspect}, but not supported! Supported operators are #{@supported}.
110
110
  MSG
111
111
  end
@@ -118,7 +118,7 @@ module Graphiti
118
118
  end
119
119
 
120
120
  def message
121
- <<-MSG
121
+ <<~MSG
122
122
  #{@sideload.parent_resource.class.name}: tried to sideload #{@sideload.name.inspect}, but more than one #{@sideload.parent_resource.model.name} was passed!
123
123
 
124
124
  This is because you marked the sideload #{@sideload.name.inspect} with single: true
@@ -139,7 +139,7 @@ module Graphiti
139
139
  end
140
140
 
141
141
  def message
142
- <<-MSG
142
+ <<~MSG
143
143
  #{@resource.class.name}: tried to sort on attribute #{@attribute.inspect}, but passed #{@direction.inspect} when only #{@allowlist.inspect} is supported.
144
144
  MSG
145
145
  end
@@ -152,7 +152,7 @@ module Graphiti
152
152
  end
153
153
 
154
154
  def message
155
- <<-MSG
155
+ <<~MSG
156
156
  #{@resource_class.name}: called .on_extra_attribute #{@name.inspect}, but extra attribute #{@name.inspect} does not exist!
157
157
  MSG
158
158
  end
@@ -173,7 +173,7 @@ module Graphiti
173
173
  else
174
174
  "value #{@value.inspect}"
175
175
  end
176
- msg = <<-MSG
176
+ msg = <<~MSG
177
177
  #{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid #{value_string}.
178
178
  MSG
179
179
  msg << "\nAllowlist: #{allow.inspect}" if allow
@@ -190,7 +190,7 @@ module Graphiti
190
190
  end
191
191
 
192
192
  def message
193
- <<-MSG
193
+ <<~MSG
194
194
  #{@resource_class.name} You declared an attribute or filter of type "#{@enum_type}" without providing a list of permitted values, which is required.
195
195
 
196
196
  When declaring an attribute:
@@ -214,7 +214,7 @@ module Graphiti
214
214
  end
215
215
 
216
216
  def message
217
- <<-MSG
217
+ <<~MSG
218
218
  #{@resource_class.name}: Cannot link to sideload #{@sideload.name.inspect}!
219
219
 
220
220
  Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists with action #{@action.inspect}, or customize the endpoint for #{@sideload.resource.class.name}.
@@ -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.try(:cursor)
219
+ end
220
+
221
+ if fields.include?(:end_cursor)
222
+ info[:endCursor] = serializers.last.try(:cursor)
223
+ end
224
+
225
+ hash[top_level_key][:pageInfo] = info
226
+ end
227
+ end
194
228
  end
195
229
  end
@@ -191,12 +191,13 @@ module Graphiti
191
191
  (@params[:page] || {}).each_pair do |name, value|
192
192
  if legacy_nested?(name)
193
193
  value.each_pair do |k, v|
194
- hash[k.to_sym] = v.to_i
194
+ hash[k.to_sym] = cast_page_param(k.to_sym, v)
195
195
  end
196
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
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)
200
201
  end
201
202
  end
202
203
  end
@@ -240,6 +241,18 @@ module Graphiti
240
241
 
241
242
  private
242
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
+
243
256
  # Try to find on this resource
244
257
  # If not there, follow the legacy logic of scalling all other
245
258
  # resource names/types
@@ -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
@@ -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
@@ -23,6 +23,8 @@ module Graphiti
23
23
  end
24
24
 
25
25
  required = att[:filterable] == :required || !!opts[:required]
26
+ schema = !!opts[:via_attribute_dsl] ? att[:schema] : opts[:schema] != false
27
+
26
28
  config[:filters][name.to_sym] = {
27
29
  aliases: aliases,
28
30
  name: name.to_sym,
@@ -32,6 +34,7 @@ module Graphiti
32
34
  single: !!opts[:single],
33
35
  dependencies: opts[:dependent],
34
36
  required: required,
37
+ schema: schema,
35
38
  operators: operators.to_hash,
36
39
  allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default),
37
40
  deny_empty: opts.fetch(:deny_empty, filters_deny_empty_by_default)
@@ -130,7 +133,7 @@ module Graphiti
130
133
  options[:sortable] ? sort(name) : config[:sorts].delete(name)
131
134
 
132
135
  if options[:filterable]
133
- filter(name, allow: options[:allow])
136
+ filter(name, allow: options[:allow], via_attribute_dsl: true)
134
137
  else
135
138
  config[:filters].delete(name)
136
139
  end
@@ -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
 
@@ -197,7 +197,7 @@ module Graphiti
197
197
  def filters(resource)
198
198
  {}.tap do |f|
199
199
  resource.filters.each_pair do |name, filter|
200
- next unless resource.attributes[name][:schema]
200
+ next unless resource.filters[name][:schema]
201
201
 
202
202
  config = {
203
203
  type: filter[:type].to_s,
@@ -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
@@ -33,12 +34,12 @@ module Graphiti
33
34
 
34
35
  # Apply default pagination proc via the Resource adapter
35
36
  def apply_standard_scope
36
- arity = resource.adapter.method(:paginate)
37
+ meth = resource.adapter.method(:paginate)
37
38
 
38
- if arity == 4 # backwards-compat
39
- resource.adapter.paginate(@scope, number, size)
40
- else
39
+ if meth.arity == 4 # backwards-compat
41
40
  resource.adapter.paginate(@scope, number, size, offset)
41
+ else
42
+ resource.adapter.paginate(@scope, number, size)
42
43
  end
43
44
  end
44
45
 
@@ -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?
69
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]
70
96
  end
71
97
 
72
98
  def number
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.2.43"
2
+ VERSION = "1.3.2"
3
3
  end
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.2.43
4
+ version: 1.3.2
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-06-08 00:00:00.000000000 Z
11
+ date: 2021-08-30 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.0.3
364
+ rubygems_version: 3.2.22
365
365
  signing_key:
366
366
  specification_version: 4
367
367
  summary: Easily build jsonapi.org-compatible APIs