graphiti 1.2.44 → 1.3.3

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: 1c0d06bf22a3732408737c7c603c500ba8f7c71f339f6782e96d93b290e2db84
4
- data.tar.gz: 47f4cadecfe0b119dd2ad3de56ff87a6cd1f59a1d2bda572a98c57d142d4a059
3
+ metadata.gz: c7bffd0c445821cf1c012a8eb6ab0c768713930a9b248babd0062eba1f59fab4
4
+ data.tar.gz: 9bdb5e60b959890f613cef21b0507c365d30918175a8d853e010261570a29772
5
5
  SHA512:
6
- metadata.gz: 13d3880b37b14c2d22f8702c8a5f09cebd6a2ce4768018f4e109e9d05666d3b0da19d5685462978cd76d3f6f5ac8400283e3298d7d558c96af3077ba83cd2955
7
- data.tar.gz: 3470e79878d650afb8ca1bddf70d510b8a0e0fdb208945307ffeb24436ab793428c6627d746200d1eb33718fd23f3e77163fbb97bf9f1147a4283e5388a53b6f
6
+ metadata.gz: 6ba108fc51d9cdec7b3cb0764db8ce2e7f441e4e9d795f786469bf8e913b44dcec6c9e20405eb4da4f17b51084ce7f5310afe6bd858deb38338a6a4e0a17f714
7
+ data.tar.gz: 370b19742df7b6b90c6412b038b3da0542b1224463f4583a02c3a659abf7b215165131fef769f16bf52b9994e6b8a3d5ba9f913bac4504b7dddbf169937c7f72
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
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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.44"
2
+ VERSION = "1.3.3"
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.44
4
+ version: 1.3.3
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-25 00:00:00.000000000 Z
11
+ date: 2021-08-31 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.1.6
365
365
  signing_key:
366
366
  specification_version: 4
367
367
  summary: Easily build jsonapi.org-compatible APIs