graphiti 1.2.16 → 1.3.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +96 -0
  3. data/.standard.yml +4 -4
  4. data/Appraisals +23 -17
  5. data/CHANGELOG.md +7 -1
  6. data/Guardfile +5 -5
  7. data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
  8. data/deprecated_generators/graphiti/resource_generator.rb +1 -1
  9. data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +2 -2
  10. data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +3 -4
  11. data/gemfiles/rails_6.gemfile +1 -1
  12. data/gemfiles/rails_6_graphiti_rails.gemfile +2 -3
  13. data/gemfiles/{rails_4.gemfile → rails_7.gemfile} +2 -2
  14. data/gemfiles/rails_7_graphiti_rails.gemfile +19 -0
  15. data/graphiti.gemspec +16 -16
  16. data/lib/graphiti/adapters/abstract.rb +20 -5
  17. data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +1 -1
  18. data/lib/graphiti/adapters/active_record/has_many_sideload.rb +1 -1
  19. data/lib/graphiti/adapters/active_record/has_one_sideload.rb +1 -1
  20. data/lib/graphiti/adapters/active_record/{inferrence.rb → inference.rb} +2 -2
  21. data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +19 -0
  22. data/lib/graphiti/adapters/active_record.rb +119 -74
  23. data/lib/graphiti/adapters/graphiti_api.rb +1 -1
  24. data/lib/graphiti/adapters/null.rb +1 -1
  25. data/lib/graphiti/adapters/persistence/associations.rb +78 -0
  26. data/lib/graphiti/configuration.rb +3 -1
  27. data/lib/graphiti/debugger.rb +12 -8
  28. data/lib/graphiti/delegates/pagination.rb +47 -13
  29. data/lib/graphiti/deserializer.rb +3 -3
  30. data/lib/graphiti/errors.rb +109 -15
  31. data/lib/graphiti/extensions/extra_attribute.rb +4 -4
  32. data/lib/graphiti/extensions/temp_id.rb +1 -1
  33. data/lib/graphiti/filter_operators.rb +0 -1
  34. data/lib/graphiti/hash_renderer.rb +198 -21
  35. data/lib/graphiti/query.rb +105 -73
  36. data/lib/graphiti/railtie.rb +5 -5
  37. data/lib/graphiti/renderer.rb +19 -1
  38. data/lib/graphiti/request_validator.rb +10 -10
  39. data/lib/graphiti/request_validators/update_validator.rb +4 -5
  40. data/lib/graphiti/request_validators/validator.rb +38 -24
  41. data/lib/graphiti/resource/configuration.rb +35 -7
  42. data/lib/graphiti/resource/dsl.rb +34 -8
  43. data/lib/graphiti/resource/interface.rb +13 -3
  44. data/lib/graphiti/resource/links.rb +3 -3
  45. data/lib/graphiti/resource/persistence.rb +2 -1
  46. data/lib/graphiti/resource/polymorphism.rb +8 -2
  47. data/lib/graphiti/resource/remote.rb +2 -2
  48. data/lib/graphiti/resource/sideloading.rb +4 -4
  49. data/lib/graphiti/resource.rb +12 -1
  50. data/lib/graphiti/resource_proxy.rb +23 -3
  51. data/lib/graphiti/runner.rb +5 -5
  52. data/lib/graphiti/schema.rb +36 -11
  53. data/lib/graphiti/schema_diff.rb +44 -4
  54. data/lib/graphiti/scope.rb +8 -10
  55. data/lib/graphiti/scoping/base.rb +3 -3
  56. data/lib/graphiti/scoping/filter.rb +36 -15
  57. data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
  58. data/lib/graphiti/scoping/paginate.rb +47 -3
  59. data/lib/graphiti/scoping/sort.rb +5 -7
  60. data/lib/graphiti/serializer.rb +49 -7
  61. data/lib/graphiti/sideload/belongs_to.rb +1 -1
  62. data/lib/graphiti/sideload/has_many.rb +19 -1
  63. data/lib/graphiti/sideload/many_to_many.rb +11 -4
  64. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +3 -4
  65. data/lib/graphiti/sideload.rb +47 -23
  66. data/lib/graphiti/stats/dsl.rb +0 -1
  67. data/lib/graphiti/stats/payload.rb +12 -9
  68. data/lib/graphiti/types.rb +15 -15
  69. data/lib/graphiti/util/attribute_check.rb +1 -1
  70. data/lib/graphiti/util/class.rb +6 -0
  71. data/lib/graphiti/util/link.rb +10 -2
  72. data/lib/graphiti/util/persistence.rb +21 -78
  73. data/lib/graphiti/util/relationship_payload.rb +4 -4
  74. data/lib/graphiti/util/remote_params.rb +9 -4
  75. data/lib/graphiti/util/remote_serializer.rb +1 -0
  76. data/lib/graphiti/util/serializer_attributes.rb +41 -11
  77. data/lib/graphiti/util/simple_errors.rb +4 -4
  78. data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
  79. data/lib/graphiti/version.rb +1 -1
  80. data/lib/graphiti.rb +6 -3
  81. metadata +46 -37
  82. data/.travis.yml +0 -59
@@ -1,8 +1,8 @@
1
1
  module Graphiti
2
2
  class Query
3
- attr_reader :resource, :association_name, :params
3
+ attr_reader :resource, :association_name, :params, :action
4
4
 
5
- def initialize(resource, params, association_name = nil, nested_include = nil, parents = [])
5
+ def initialize(resource, params, association_name = nil, nested_include = nil, parents = [], action = nil)
6
6
  @resource = resource
7
7
  @association_name = association_name
8
8
  @params = params
@@ -11,6 +11,7 @@ module Graphiti
11
11
  @params = @params.deep_symbolize_keys
12
12
  @include_param = nested_include || @params[:include]
13
13
  @parents = parents
14
+ @action = parse_action(action)
14
15
  end
15
16
 
16
17
  def association?
@@ -31,7 +32,9 @@ module Graphiti
31
32
  end
32
33
 
33
34
  def pagination_links?
34
- if Graphiti.config.pagination_links_on_demand
35
+ if action == :find
36
+ false
37
+ elsif Graphiti.config.pagination_links_on_demand
35
38
  [true, "true"].include?(@params[:pagination_links])
36
39
  else
37
40
  Graphiti.config.pagination_links
@@ -44,16 +47,19 @@ module Graphiti
44
47
 
45
48
  def hash
46
49
  @hash ||= {}.tap do |h|
47
- h[:filter] = filters unless filters.empty?
48
- h[:sort] = sorts unless sorts.empty?
49
- h[:page] = pagination unless pagination.empty?
50
- unless association?
51
- h[:fields] = fields unless fields.empty?
52
- h[:extra_fields] = extra_fields unless extra_fields.empty?
50
+ h[:filter] = filters
51
+ h[:sort] = sorts
52
+ h[:page] = pagination
53
+ if association?
54
+ resource_type = @resource.class.type
55
+ h[:extra_fields] = {resource_type => extra_fields[resource_type]} if extra_fields.key?(resource_type)
56
+ else
57
+ h[:fields] = fields
58
+ h[:extra_fields] = extra_fields
53
59
  end
54
- h[:stats] = stats unless stats.empty?
55
- h[:include] = sideload_hash unless sideload_hash.empty?
56
- end
60
+ h[:stats] = stats
61
+ h[:include] = sideload_hash
62
+ end.reject { |_, value| value.empty? }
57
63
  end
58
64
 
59
65
  def zero_results?
@@ -63,11 +69,9 @@ module Graphiti
63
69
  end
64
70
 
65
71
  def sideload_hash
66
- @sideload_hash = begin
67
- {}.tap do |hash|
68
- sideloads.each_pair do |key, value|
69
- hash[key] = sideloads[key].hash
70
- end
72
+ @sideload_hash = {}.tap do |hash|
73
+ sideloads.each_pair do |key, value|
74
+ hash[key] = sideloads[key].hash
71
75
  end
72
76
  end
73
77
  end
@@ -83,19 +87,28 @@ module Graphiti
83
87
  end
84
88
 
85
89
  def sideloads
86
- @sideloads ||= begin
87
- {}.tap do |hash|
88
- include_hash.each_pair do |key, sub_hash|
89
- sideload = @resource.class.sideload(key)
90
-
91
- if sideload || @resource.remote?
92
- sl_resource = resource_for_sideload(sideload)
93
- query_parents = parents + [self]
94
- sub_hash = sub_hash[:include] if sub_hash.key?(:include)
95
- hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents)
96
- else
97
- handle_missing_sideload(key)
98
- 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)
99
112
  end
100
113
  end
101
114
  end
@@ -120,27 +133,25 @@ module Graphiti
120
133
  end
121
134
 
122
135
  def filters
123
- @filters ||= begin
124
- {}.tap do |hash|
125
- (@params[:filter] || {}).each_pair do |name, value|
126
- name = name.to_sym
127
-
128
- if legacy_nested?(name)
129
- value.keys.each do |key|
130
- filter_name = key.to_sym
131
- filter_value = value[key]
132
-
133
- if @resource.get_attr!(filter_name, :filterable, request: true)
134
- hash[filter_name] = filter_value
135
- 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
136
147
  end
137
- elsif nested?(name)
138
- name = name.to_s.split(".").last.to_sym
139
- validate!(name, :filterable)
140
- hash[name] = value
141
- elsif top_level? && validate!(name, :filterable)
142
- hash[name] = value
143
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
144
155
  end
145
156
  end
146
157
  end
@@ -169,18 +180,17 @@ module Graphiti
169
180
  end
170
181
 
171
182
  def pagination
172
- @pagination ||= begin
173
- {}.tap do |hash|
174
- (@params[:page] || {}).each_pair do |name, value|
175
- if legacy_nested?(name)
176
- value.each_pair do |k, v|
177
- hash[k.to_sym] = v.to_i
178
- end
179
- elsif nested?(name)
180
- hash[name.to_s.split(".").last.to_sym] = value
181
- elsif top_level? && [:number, :size].include?(name.to_sym)
182
- 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)
183
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)
184
194
  end
185
195
  end
186
196
  end
@@ -203,15 +213,13 @@ module Graphiti
203
213
  end
204
214
 
205
215
  def stats
206
- @stats ||= begin
207
- {}.tap do |hash|
208
- (@params[:stats] || {}).each_pair do |k, v|
209
- if legacy_nested?(k)
210
- raise NotImplementedError.new("Association statistics are not currently supported")
211
- elsif top_level?
212
- v = v.split(",") if v.is_a?(String)
213
- hash[k.to_sym] = Array(v).flatten.map(&:to_sym)
214
- 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)
215
223
  end
216
224
  end
217
225
  end
@@ -223,6 +231,18 @@ module Graphiti
223
231
 
224
232
  private
225
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
+
226
246
  # Try to find on this resource
227
247
  # If not there, follow the legacy logic of scalling all other
228
248
  # resource names/types
@@ -232,7 +252,7 @@ module Graphiti
232
252
  return true if @resource.remote?
233
253
 
234
254
  if (att = @resource.get_attr(name, flag, request: true))
235
- return att
255
+ att
236
256
  else
237
257
  not_associated_name = !@resource.class.association_names.include?(name)
238
258
  not_associated_type = !@resource.class.association_types.include?(name)
@@ -262,8 +282,8 @@ module Graphiti
262
282
  def parse_fieldset(fieldset)
263
283
  {}.tap do |hash|
264
284
  fieldset.each_pair do |type, fields|
265
- type = type.to_sym
266
- fields = fields.split(",") unless fields.is_a?(Array)
285
+ type = type.to_sym
286
+ fields = fields.split(",") unless fields.is_a?(Array)
267
287
  hash[type] = fields.map(&:to_sym)
268
288
  end
269
289
  end
@@ -282,7 +302,7 @@ module Graphiti
282
302
 
283
303
  def sort_hash(attr)
284
304
  value = attr[0] == "-" ? :desc : :asc
285
- key = attr.sub("-", "").to_sym
305
+ key = attr.sub("-", "").to_sym
286
306
 
287
307
  {key => value}
288
308
  end
@@ -312,5 +332,17 @@ module Graphiti
312
332
  end
313
333
  end
314
334
  end
335
+
336
+ def parse_action(action)
337
+ action ||= @params.fetch(:action, Graphiti.context[:namespace]).try(:to_sym)
338
+ case action
339
+ when :index
340
+ :all
341
+ when :show
342
+ :find
343
+ else
344
+ action
345
+ end
346
+ end
315
347
  end
316
348
  end
@@ -6,7 +6,7 @@ module Graphiti
6
6
  end
7
7
 
8
8
  generators do
9
- Dir[File.expand_path("../../deprecated_generators/**/*.rb", __dir__)].each do |f|
9
+ Dir[File.expand_path("../../deprecated_generators/**/*.rb", __dir__)].sort.each do |f|
10
10
  require f
11
11
  end
12
12
  end
@@ -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
@@ -17,8 +17,20 @@ module Graphiti
17
17
  render(self.class.jsonapi_renderer).to_json
18
18
  end
19
19
 
20
+ def as_graphql
21
+ render(self.class.graphql_renderer(@proxy))
22
+ end
23
+
24
+ def to_graphql
25
+ as_graphql.to_json
26
+ end
27
+
20
28
  def to_json
21
- render(self.class.hash_renderer(@proxy)).to_json
29
+ as_json.to_json
30
+ end
31
+
32
+ def as_json
33
+ render(self.class.hash_renderer(@proxy))
22
34
  end
23
35
 
24
36
  def to_xml
@@ -35,6 +47,11 @@ module Graphiti
35
47
  JSONAPI::Serializable::Renderer.new(implementation)
36
48
  end
37
49
 
50
+ def self.graphql_renderer(proxy)
51
+ implementation = Graphiti::HashRenderer.new(proxy.resource, graphql: true)
52
+ JSONAPI::Serializable::Renderer.new(implementation)
53
+ end
54
+
38
55
  private
39
56
 
40
57
  def render(renderer)
@@ -49,6 +66,7 @@ module Graphiti
49
66
  options[:meta] ||= proxy.meta
50
67
  options[:meta][:stats] = proxy.stats unless proxy.stats.empty?
51
68
  options[:meta][:debug] = Debugger.to_a if debug_json?
69
+ options[:proxy] = proxy
52
70
 
53
71
  renderer.render(records, options)
54
72
  end
@@ -1,23 +1,23 @@
1
1
  module Graphiti
2
2
  class RequestValidator
3
3
  delegate :validate,
4
- :validate!,
5
- :errors,
6
- :deserialized_payload,
7
- to: :@validator
4
+ :validate!,
5
+ :errors,
6
+ :deserialized_payload,
7
+ to: :@validator
8
8
 
9
- def initialize(root_resource, raw_params)
10
- @validator = ValidatorFactory.create(root_resource, raw_params)
9
+ def initialize(root_resource, raw_params, action)
10
+ @validator = ValidatorFactory.create(root_resource, raw_params, action)
11
11
  end
12
12
 
13
13
  class ValidatorFactory
14
- def self.create(root_resource, raw_params)
15
- case raw_params["action"]
16
- when "update" then
14
+ def self.create(root_resource, raw_params, action)
15
+ case action
16
+ when :update
17
17
  RequestValidators::UpdateValidator
18
18
  else
19
19
  RequestValidators::Validator
20
- end.new(root_resource, raw_params)
20
+ end.new(root_resource, raw_params, action)
21
21
  end
22
22
  end
23
23
  end
@@ -5,7 +5,7 @@ module Graphiti
5
5
  if required_payload? && payload_matches_endpoint?
6
6
  super
7
7
  else
8
- return false
8
+ false
9
9
  end
10
10
  end
11
11
 
@@ -26,18 +26,17 @@ module Graphiti
26
26
  [:data, :type],
27
27
  [:data, :id]
28
28
  ].each do |required_attr|
29
- attribute_mismatch(required_attr) unless @raw_params.dig(*required_attr)
29
+ attribute_mismatch(required_attr) unless @params.dig(*required_attr)
30
30
  end
31
31
  errors.blank?
32
32
  end
33
33
 
34
34
  def payload_matches_endpoint?
35
- unless @raw_params.dig(:data, :id) == @raw_params.dig(:filter, :id)
35
+ unless @params.dig(:data, :id) == @params.dig(:filter, :id)
36
36
  attribute_mismatch([:data, :id])
37
37
  end
38
38
 
39
-
40
- meta_type = @raw_params.dig(:data, :type)
39
+ meta_type = @params.dig(:data, :type)
41
40
 
42
41
  # NOTE: calling #to_s and comparing 2 strings is slower than
43
42
  # calling #to_sym and comparing 2 symbols. But pre ruby-2.2
@@ -3,43 +3,47 @@ module Graphiti
3
3
  class Validator
4
4
  attr_reader :errors
5
5
 
6
- def initialize(root_resource, raw_params)
6
+ def initialize(root_resource, raw_params, action)
7
7
  @root_resource = root_resource
8
- @raw_params = raw_params
8
+ @params = normalized_params(raw_params)
9
9
  @errors = Graphiti::Util::SimpleErrors.new(raw_params)
10
+ @action = action
10
11
  end
11
12
 
12
13
  def validate
14
+ # Right now, all requests - even reads - go through the validator
15
+ # In the future these should have their own validation logic, but
16
+ # for now we can just bypass
17
+ return true unless @params.has_key?(:data)
18
+
13
19
  resource = @root_resource
14
- if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
15
- if @root_resource.type != meta_type && @root_resource.polymorphic?
16
- resource = @root_resource.class.resource_for_type(meta_type).new
20
+
21
+ if @params[:data].has_key?(:type)
22
+ if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
23
+ if @root_resource.type != meta_type && @root_resource.polymorphic?
24
+ resource = @root_resource.class.resource_for_type(meta_type).new
25
+ end
17
26
  end
18
- end
19
27
 
20
- typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
21
- process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
28
+ typecast_attributes(resource, deserialized_payload.attributes, @action, deserialized_payload.meta[:payload_path])
29
+ process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
30
+ else
31
+ errors.add(:"data.type", :missing)
32
+ end
22
33
 
23
34
  errors.blank?
24
35
  end
25
36
 
26
37
  def validate!
27
38
  unless validate
28
- raise @error_class || Graphiti::Errors::InvalidRequest, self.errors
39
+ raise @error_class || Graphiti::Errors::InvalidRequest, errors
29
40
  end
30
41
 
31
42
  true
32
43
  end
33
44
 
34
45
  def deserialized_payload
35
- @deserialized_payload ||= begin
36
- payload = normalized_params
37
- if payload[:data] && payload[:data][:type]
38
- Graphiti::Deserializer.new(payload)
39
- else
40
- Graphiti::Deserializer.new({})
41
- end
42
- end
46
+ @deserialized_payload ||= Graphiti::Deserializer.new(@params)
43
47
  end
44
48
 
45
49
  private
@@ -47,10 +51,10 @@ module Graphiti
47
51
  def process_relationships(resource, relationships, payload_path)
48
52
  opts = {
49
53
  resource: resource,
50
- relationships: relationships,
54
+ relationships: relationships
51
55
  }
52
56
 
53
- Graphiti::Util::RelationshipPayload.iterate(opts) do |x|
57
+ Graphiti::Util::RelationshipPayload.iterate(**opts) do |x|
54
58
  sideload_def = x[:sideload]
55
59
 
56
60
  unless sideload_def.writable?
@@ -61,13 +65,23 @@ module Graphiti
61
65
  next
62
66
  end
63
67
 
64
- typecast_attributes(x[:resource], x[:attributes], x[:meta][:payload_path])
65
- process_relationships(x[:resource], x[:relationships], x[:meta][:payload_path])
68
+ resource = x[:resource]
69
+ attributes = x[:attributes]
70
+ relationships = x[:relationships]
71
+ payload_path = x[:meta][:payload_path]
72
+ action = x[:meta][:method]
73
+ typecast_attributes(resource, attributes, action, payload_path)
74
+ process_relationships(resource, relationships, payload_path)
66
75
  end
67
76
  end
68
77
 
69
- def typecast_attributes(resource, attributes, payload_path)
78
+ def typecast_attributes(resource, attributes, action, payload_path)
70
79
  attributes.each_pair do |key, value|
80
+ # Only validate id if create action, otherwise it's only used for lookup
81
+ next if action != :create &&
82
+ key == :id &&
83
+ resource.class.config[:attributes][:id][:writable] == false
84
+
71
85
  begin
72
86
  attributes[key] = resource.typecast(key, value, :writable)
73
87
  rescue Graphiti::Errors::UnknownAttribute
@@ -80,8 +94,8 @@ module Graphiti
80
94
  end
81
95
  end
82
96
 
83
- def normalized_params
84
- normalized = @raw_params
97
+ def normalized_params(raw_params)
98
+ normalized = raw_params
85
99
  if normalized.respond_to?(:to_unsafe_h)
86
100
  normalized = normalized.to_unsafe_h.deep_symbolize_keys
87
101
  end
@@ -22,12 +22,20 @@ module Graphiti
22
22
  end
23
23
 
24
24
  def type=(val)
25
- val = val && val.to_sym
25
+ val = val&.to_sym
26
26
  if (val = super)
27
27
  serializer.type(val)
28
28
  end
29
29
  end
30
30
 
31
+ def graphql_entrypoint=(val)
32
+ if val
33
+ super(val.to_s.camelize(:lower).to_sym)
34
+ else
35
+ super
36
+ end
37
+ end
38
+
31
39
  # The .stat call stores a proc based on adapter
32
40
  # So if we assign a new adapter, reconfigure
33
41
  def adapter=(val)
@@ -42,7 +50,7 @@ module Graphiti
42
50
  path: val,
43
51
  full_path: val,
44
52
  url: val,
45
- actions: [:index, :show],
53
+ actions: [:index, :show]
46
54
  }
47
55
  end
48
56
 
@@ -82,7 +90,10 @@ module Graphiti
82
90
  :attributes_schema_by_default,
83
91
  :relationships_readable_by_default,
84
92
  :relationships_writable_by_default,
85
- :filters_accept_nil_by_default
93
+ :filters_accept_nil_by_default,
94
+ :filters_deny_empty_by_default,
95
+ :graphql_entrypoint,
96
+ :cursor_paginatable
86
97
 
87
98
  class << self
88
99
  prepend Overrides
@@ -96,6 +107,7 @@ module Graphiti
96
107
  # re-assigning causes a new Class.new
97
108
  klass.serializer = (klass.serializer || klass.infer_serializer_superclass)
98
109
  klass.type ||= klass.infer_type
110
+ klass.graphql_entrypoint = klass.type.to_s.pluralize.to_sym
99
111
  default(klass, :attributes_readable_by_default, true)
100
112
  default(klass, :attributes_writable_by_default, true)
101
113
  default(klass, :attributes_sortable_by_default, true)
@@ -104,6 +116,7 @@ module Graphiti
104
116
  default(klass, :relationships_readable_by_default, true)
105
117
  default(klass, :relationships_writable_by_default, true)
106
118
  default(klass, :filters_accept_nil_by_default, false)
119
+ default(klass, :filters_deny_empty_by_default, false)
107
120
 
108
121
  unless klass.config[:attributes][:id]
109
122
  klass.attribute :id, :integer_id
@@ -127,7 +140,7 @@ module Graphiti
127
140
  def get_attr(name, flag, opts = {})
128
141
  defaults = {request: false}
129
142
  opts = defaults.merge(opts)
130
- new.get_attr(name, flag, opts)
143
+ new.get_attr(name, flag, **opts)
131
144
  end
132
145
 
133
146
  def abstract_class?
@@ -142,19 +155,20 @@ module Graphiti
142
155
  if (@abstract_class = val)
143
156
  self.serializer = nil
144
157
  self.type = nil
158
+ self.graphql_entrypoint = nil
145
159
  end
146
160
  end
147
161
 
148
162
  def infer_type
149
163
  if name.present?
150
- name.demodulize.gsub("Resource", "").underscore.pluralize.to_sym
164
+ name.demodulize.sub(/.*\KResource/, "").underscore.pluralize.to_sym
151
165
  else
152
166
  :undefined_jsonapi_type
153
167
  end
154
168
  end
155
169
 
156
170
  def infer_model
157
- name&.gsub("Resource", "")&.safe_constantize
171
+ name&.sub(/.*\KResource/, "")&.safe_constantize
158
172
  end
159
173
 
160
174
  # @api private
@@ -186,6 +200,7 @@ module Graphiti
186
200
  @config ||=
187
201
  {
188
202
  filters: {},
203
+ grouped_filters: {},
189
204
  default_filters: {},
190
205
  stats: {},
191
206
  sort_all: nil,
@@ -198,6 +213,7 @@ module Graphiti
198
213
  extra_attributes: {},
199
214
  sideloads: {},
200
215
  callbacks: {},
216
+ links: {}
201
217
  }
202
218
  end
203
219
 
@@ -221,6 +237,10 @@ module Graphiti
221
237
  config[:filters]
222
238
  end
223
239
 
240
+ def grouped_filters
241
+ config[:grouped_filters]
242
+ end
243
+
224
244
  def sorts
225
245
  config[:sorts]
226
246
  end
@@ -236,11 +256,15 @@ module Graphiti
236
256
  def default_filters
237
257
  config[:default_filters]
238
258
  end
259
+
260
+ def links
261
+ config[:links]
262
+ end
239
263
  end
240
264
 
241
265
  def get_attr!(name, flag, options = {})
242
266
  options[:raise_error] = true
243
- get_attr(name, flag, options)
267
+ get_attr(name, flag, **options)
244
268
  end
245
269
 
246
270
  def get_attr(name, flag, request: false, raise_error: false)
@@ -255,6 +279,10 @@ module Graphiti
255
279
  self.class.filters
256
280
  end
257
281
 
282
+ def grouped_filters
283
+ self.class.grouped_filters
284
+ end
285
+
258
286
  def sort_all
259
287
  self.class.sort_all
260
288
  end