graphiti 1.2.16 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +96 -0
- data/.standard.yml +4 -4
- data/Appraisals +23 -17
- data/CHANGELOG.md +7 -1
- data/Guardfile +5 -5
- data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
- data/deprecated_generators/graphiti/resource_generator.rb +1 -1
- data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +2 -2
- data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +3 -4
- data/gemfiles/rails_6.gemfile +1 -1
- data/gemfiles/rails_6_graphiti_rails.gemfile +2 -3
- data/gemfiles/{rails_4.gemfile → rails_7.gemfile} +2 -2
- data/gemfiles/rails_7_graphiti_rails.gemfile +19 -0
- data/graphiti.gemspec +16 -16
- data/lib/graphiti/adapters/abstract.rb +20 -5
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/{inferrence.rb → inference.rb} +2 -2
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +19 -0
- data/lib/graphiti/adapters/active_record.rb +119 -74
- data/lib/graphiti/adapters/graphiti_api.rb +1 -1
- data/lib/graphiti/adapters/null.rb +1 -1
- data/lib/graphiti/adapters/persistence/associations.rb +78 -0
- data/lib/graphiti/configuration.rb +3 -1
- data/lib/graphiti/debugger.rb +12 -8
- data/lib/graphiti/delegates/pagination.rb +47 -13
- data/lib/graphiti/deserializer.rb +3 -3
- data/lib/graphiti/errors.rb +109 -15
- data/lib/graphiti/extensions/extra_attribute.rb +4 -4
- data/lib/graphiti/extensions/temp_id.rb +1 -1
- data/lib/graphiti/filter_operators.rb +0 -1
- data/lib/graphiti/hash_renderer.rb +198 -21
- data/lib/graphiti/query.rb +105 -73
- data/lib/graphiti/railtie.rb +5 -5
- data/lib/graphiti/renderer.rb +19 -1
- data/lib/graphiti/request_validator.rb +10 -10
- data/lib/graphiti/request_validators/update_validator.rb +4 -5
- data/lib/graphiti/request_validators/validator.rb +38 -24
- data/lib/graphiti/resource/configuration.rb +35 -7
- data/lib/graphiti/resource/dsl.rb +34 -8
- data/lib/graphiti/resource/interface.rb +13 -3
- data/lib/graphiti/resource/links.rb +3 -3
- data/lib/graphiti/resource/persistence.rb +2 -1
- data/lib/graphiti/resource/polymorphism.rb +8 -2
- data/lib/graphiti/resource/remote.rb +2 -2
- data/lib/graphiti/resource/sideloading.rb +4 -4
- data/lib/graphiti/resource.rb +12 -1
- data/lib/graphiti/resource_proxy.rb +23 -3
- data/lib/graphiti/runner.rb +5 -5
- data/lib/graphiti/schema.rb +36 -11
- data/lib/graphiti/schema_diff.rb +44 -4
- data/lib/graphiti/scope.rb +8 -10
- data/lib/graphiti/scoping/base.rb +3 -3
- data/lib/graphiti/scoping/filter.rb +36 -15
- data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
- data/lib/graphiti/scoping/paginate.rb +47 -3
- data/lib/graphiti/scoping/sort.rb +5 -7
- data/lib/graphiti/serializer.rb +49 -7
- data/lib/graphiti/sideload/belongs_to.rb +1 -1
- data/lib/graphiti/sideload/has_many.rb +19 -1
- data/lib/graphiti/sideload/many_to_many.rb +11 -4
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +3 -4
- data/lib/graphiti/sideload.rb +47 -23
- data/lib/graphiti/stats/dsl.rb +0 -1
- data/lib/graphiti/stats/payload.rb +12 -9
- data/lib/graphiti/types.rb +15 -15
- data/lib/graphiti/util/attribute_check.rb +1 -1
- data/lib/graphiti/util/class.rb +6 -0
- data/lib/graphiti/util/link.rb +10 -2
- data/lib/graphiti/util/persistence.rb +21 -78
- data/lib/graphiti/util/relationship_payload.rb +4 -4
- data/lib/graphiti/util/remote_params.rb +9 -4
- data/lib/graphiti/util/remote_serializer.rb +1 -0
- data/lib/graphiti/util/serializer_attributes.rb +41 -11
- data/lib/graphiti/util/simple_errors.rb +4 -4
- data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +6 -3
- metadata +46 -37
- data/.travis.yml +0 -59
data/lib/graphiti/query.rb
CHANGED
@@ -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
|
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
|
48
|
-
h[:sort] = sorts
|
49
|
-
h[:page] = pagination
|
50
|
-
|
51
|
-
|
52
|
-
h[:extra_fields] = extra_fields
|
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
|
55
|
-
h[:include] = sideload_hash
|
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 =
|
67
|
-
|
68
|
-
|
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 ||=
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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 ||=
|
124
|
-
{}.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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 ||=
|
173
|
-
{}.
|
174
|
-
(
|
175
|
-
|
176
|
-
|
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 ||=
|
207
|
-
{}.
|
208
|
-
(
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
-
|
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
|
266
|
-
fields
|
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
|
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
|
data/lib/graphiti/railtie.rb
CHANGED
@@ -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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
data/lib/graphiti/renderer.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
16
|
-
when
|
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
|
-
|
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 @
|
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 @
|
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
|
-
@
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
21
|
-
|
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,
|
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 ||=
|
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
|
-
|
65
|
-
|
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 =
|
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
|
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.
|
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&.
|
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
|