jsonapi-resources 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +274 -102
- data/jsonapi-resources.gemspec +1 -0
- data/lib/jsonapi-resources.rb +15 -0
- data/lib/jsonapi/active_record_operations_processor.rb +21 -10
- data/lib/jsonapi/acts_as_resource_controller.rb +175 -0
- data/lib/jsonapi/configuration.rb +11 -0
- data/lib/jsonapi/error_codes.rb +7 -4
- data/lib/jsonapi/exceptions.rb +23 -15
- data/lib/jsonapi/formatter.rb +5 -5
- data/lib/jsonapi/include_directives.rb +67 -0
- data/lib/jsonapi/operation.rb +185 -65
- data/lib/jsonapi/operation_result.rb +38 -5
- data/lib/jsonapi/operation_results.rb +33 -0
- data/lib/jsonapi/operations_processor.rb +49 -9
- data/lib/jsonapi/paginator.rb +31 -17
- data/lib/jsonapi/request.rb +347 -163
- data/lib/jsonapi/resource.rb +159 -56
- data/lib/jsonapi/resource_controller.rb +1 -234
- data/lib/jsonapi/resource_serializer.rb +55 -69
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +87 -0
- data/lib/jsonapi/routing_ext.rb +17 -11
- data/test/controllers/controller_test.rb +602 -326
- data/test/fixtures/active_record.rb +96 -6
- data/test/fixtures/line_items.yml +7 -1
- data/test/fixtures/numeros_telefone.yml +3 -0
- data/test/fixtures/purchase_orders.yml +6 -0
- data/test/integration/requests/request_test.rb +129 -60
- data/test/integration/routes/routes_test.rb +17 -17
- data/test/test_helper.rb +23 -5
- data/test/unit/jsonapi_request/jsonapi_request_test.rb +48 -0
- data/test/unit/operation/operations_processor_test.rb +242 -54
- data/test/unit/resource/resource_test.rb +108 -2
- data/test/unit/serializer/include_directives_test.rb +108 -0
- data/test/unit/serializer/response_document_test.rb +61 -0
- data/test/unit/serializer/serializer_test.rb +679 -520
- metadata +26 -2
data/lib/jsonapi/paginator.rb
CHANGED
@@ -3,7 +3,7 @@ module JSONAPI
|
|
3
3
|
def initialize(params)
|
4
4
|
end
|
5
5
|
|
6
|
-
def apply(relation)
|
6
|
+
def apply(relation, order_options)
|
7
7
|
# relation
|
8
8
|
end
|
9
9
|
|
@@ -19,9 +19,10 @@ end
|
|
19
19
|
class OffsetPaginator < JSONAPI::Paginator
|
20
20
|
def initialize(params)
|
21
21
|
parse_pagination_params(params)
|
22
|
+
verify_pagination_params
|
22
23
|
end
|
23
24
|
|
24
|
-
def apply(relation)
|
25
|
+
def apply(relation, order_options)
|
25
26
|
relation.offset(@offset).limit(@limit)
|
26
27
|
end
|
27
28
|
|
@@ -35,27 +36,34 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
35
36
|
|
36
37
|
@offset = validparams[:offset] ? validparams[:offset].to_i : 0
|
37
38
|
@limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
|
38
|
-
|
39
|
-
if @limit < 1
|
40
|
-
raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, validparams[:limit])
|
41
|
-
elsif @limit > JSONAPI.configuration.maximum_page_size
|
42
|
-
raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, validparams[:limit],
|
43
|
-
"Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
44
|
-
end
|
45
39
|
else
|
46
40
|
raise JSONAPI::Exceptions::InvalidPageObject.new
|
47
41
|
end
|
48
42
|
rescue ActionController::UnpermittedParameters => e
|
49
43
|
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
50
44
|
end
|
45
|
+
|
46
|
+
def verify_pagination_params
|
47
|
+
if @limit < 1
|
48
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
|
49
|
+
elsif @limit > JSONAPI.configuration.maximum_page_size
|
50
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
|
51
|
+
"Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
52
|
+
end
|
53
|
+
|
54
|
+
if @offset < 0
|
55
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
|
56
|
+
end
|
57
|
+
end
|
51
58
|
end
|
52
59
|
|
53
60
|
class PagedPaginator < JSONAPI::Paginator
|
54
61
|
def initialize(params)
|
55
62
|
parse_pagination_params(params)
|
63
|
+
verify_pagination_params
|
56
64
|
end
|
57
65
|
|
58
|
-
def apply(relation)
|
66
|
+
def apply(relation, order_options)
|
59
67
|
offset = (@number - 1) * @size
|
60
68
|
relation.offset(offset).limit(@size)
|
61
69
|
end
|
@@ -70,13 +78,6 @@ class PagedPaginator < JSONAPI::Paginator
|
|
70
78
|
|
71
79
|
@size = validparams[:size] ? validparams[:size].to_i : JSONAPI.configuration.default_page_size
|
72
80
|
@number = validparams[:number] ? validparams[:number].to_i : 1
|
73
|
-
|
74
|
-
if @size < 1
|
75
|
-
raise JSONAPI::Exceptions::InvalidPageValue.new(:size, validparams[:size])
|
76
|
-
elsif @size > JSONAPI.configuration.maximum_page_size
|
77
|
-
raise JSONAPI::Exceptions::InvalidPageValue.new(:size, validparams[:size],
|
78
|
-
"size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
79
|
-
end
|
80
81
|
else
|
81
82
|
@size = JSONAPI.configuration.default_page_size
|
82
83
|
@number = params.to_i
|
@@ -84,4 +85,17 @@ class PagedPaginator < JSONAPI::Paginator
|
|
84
85
|
rescue ActionController::UnpermittedParameters => e
|
85
86
|
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
86
87
|
end
|
88
|
+
|
89
|
+
def verify_pagination_params
|
90
|
+
if @size < 1
|
91
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
|
92
|
+
elsif @size > JSONAPI.configuration.maximum_page_size
|
93
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
|
94
|
+
"size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
95
|
+
end
|
96
|
+
|
97
|
+
if @number < 1
|
98
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
|
99
|
+
end
|
100
|
+
end
|
87
101
|
end
|
data/lib/jsonapi/request.rb
CHANGED
@@ -4,71 +4,121 @@ require 'jsonapi/paginator'
|
|
4
4
|
module JSONAPI
|
5
5
|
class Request
|
6
6
|
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
|
7
|
-
:resource_klass, :context, :paginator, :source_klass, :source_id
|
7
|
+
:resource_klass, :context, :paginator, :source_klass, :source_id,
|
8
|
+
:include_directives
|
8
9
|
|
9
10
|
def initialize(params = nil, options = {})
|
10
|
-
@context = options
|
11
|
+
@context = options[:context]
|
11
12
|
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
12
13
|
@errors = []
|
13
14
|
@operations = []
|
14
15
|
@fields = {}
|
15
|
-
@include = []
|
16
16
|
@filters = {}
|
17
|
-
@sort_criteria = []
|
17
|
+
@sort_criteria = [{field: 'id', direction: :asc}]
|
18
18
|
@source_klass = nil
|
19
19
|
@source_id = nil
|
20
|
+
@include_directives = nil
|
21
|
+
@paginator = nil
|
22
|
+
@id = nil
|
20
23
|
|
21
|
-
|
24
|
+
setup_action(params)
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
27
|
+
def setup_action(params)
|
28
|
+
return if params.nil?
|
29
|
+
|
25
30
|
@resource_klass ||= Resource.resource_for(params[:controller]) if params[:controller]
|
26
31
|
|
27
32
|
unless params.nil?
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
parse_include(params[:include])
|
32
|
-
parse_filters(params[:filter])
|
33
|
-
parse_sort_criteria(params[:sort])
|
34
|
-
parse_pagination(params[:page])
|
35
|
-
when 'get_related_resource', 'get_related_resources'
|
36
|
-
@source_klass = Resource.resource_for(params.require(:source))
|
37
|
-
@source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
|
38
|
-
parse_fields(params[:fields])
|
39
|
-
parse_include(params[:include])
|
40
|
-
parse_filters(params[:filter])
|
41
|
-
parse_sort_criteria(params[:sort])
|
42
|
-
parse_pagination(params[:page])
|
43
|
-
when 'show'
|
44
|
-
parse_fields(params[:fields])
|
45
|
-
parse_include(params[:include])
|
46
|
-
when 'create'
|
47
|
-
parse_fields(params[:fields])
|
48
|
-
parse_include(params[:include])
|
49
|
-
parse_add_operation(params.require(:data))
|
50
|
-
when 'create_association'
|
51
|
-
parse_add_association_operation(params.require(:data),
|
52
|
-
params.require(:association),
|
53
|
-
params.require(@resource_klass._as_parent_key))
|
54
|
-
when 'update_association'
|
55
|
-
parse_update_association_operation(params.fetch(:data),
|
56
|
-
params.require(:association),
|
57
|
-
params.require(@resource_klass._as_parent_key))
|
58
|
-
when 'update'
|
59
|
-
parse_fields(params[:fields])
|
60
|
-
parse_include(params[:include])
|
61
|
-
parse_replace_operation(params.require(:data), params.require(@resource_klass._primary_key))
|
62
|
-
when 'destroy'
|
63
|
-
parse_remove_operation(params)
|
64
|
-
when 'destroy_association'
|
65
|
-
parse_remove_association_operation(params)
|
33
|
+
setup_action_method_name = "setup_#{params[:action]}_action"
|
34
|
+
if respond_to?(setup_action_method_name)
|
35
|
+
send(setup_action_method_name, params)
|
66
36
|
end
|
67
37
|
end
|
68
38
|
rescue ActionController::ParameterMissing => e
|
69
39
|
@errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
|
70
40
|
end
|
71
41
|
|
42
|
+
def setup_index_action(params)
|
43
|
+
parse_fields(params[:fields])
|
44
|
+
parse_include_directives(params[:include])
|
45
|
+
set_default_filters
|
46
|
+
parse_filters(params[:filter])
|
47
|
+
parse_sort_criteria(params[:sort])
|
48
|
+
parse_pagination(params[:page])
|
49
|
+
add_find_operation
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_get_related_resource_action(params)
|
53
|
+
initialize_source(params)
|
54
|
+
parse_fields(params[:fields])
|
55
|
+
parse_include_directives(params[:include])
|
56
|
+
set_default_filters
|
57
|
+
parse_filters(params[:filter])
|
58
|
+
parse_sort_criteria(params[:sort])
|
59
|
+
parse_pagination(params[:page])
|
60
|
+
add_show_related_resource_operation(params[:association])
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_get_related_resources_action(params)
|
64
|
+
initialize_source(params)
|
65
|
+
parse_fields(params[:fields])
|
66
|
+
parse_include_directives(params[:include])
|
67
|
+
set_default_filters
|
68
|
+
parse_filters(params[:filter])
|
69
|
+
parse_sort_criteria(params[:sort])
|
70
|
+
parse_pagination(params[:page])
|
71
|
+
add_show_related_resources_operation(params[:association])
|
72
|
+
end
|
73
|
+
|
74
|
+
def setup_show_action(params)
|
75
|
+
parse_fields(params[:fields])
|
76
|
+
parse_include_directives(params[:include])
|
77
|
+
@id = params[:id]
|
78
|
+
add_show_operation
|
79
|
+
end
|
80
|
+
|
81
|
+
def setup_show_association_action(params)
|
82
|
+
add_show_association_operation(params[:association], params.require(@resource_klass._as_parent_key))
|
83
|
+
end
|
84
|
+
|
85
|
+
def setup_create_action(params)
|
86
|
+
parse_fields(params[:fields])
|
87
|
+
parse_include_directives(params[:include])
|
88
|
+
parse_add_operation(params.require(:data))
|
89
|
+
end
|
90
|
+
|
91
|
+
def setup_create_association_action(params)
|
92
|
+
parse_add_association_operation(params.require(:data),
|
93
|
+
params.require(:association),
|
94
|
+
params.require(@resource_klass._as_parent_key))
|
95
|
+
end
|
96
|
+
|
97
|
+
def setup_update_association_action(params)
|
98
|
+
parse_update_association_operation(params.fetch(:data),
|
99
|
+
params.require(:association),
|
100
|
+
params.require(@resource_klass._as_parent_key))
|
101
|
+
end
|
102
|
+
|
103
|
+
def setup_update_action(params)
|
104
|
+
parse_fields(params[:fields])
|
105
|
+
parse_include_directives(params[:include])
|
106
|
+
parse_replace_operation(params.require(:data), params.require(:id))
|
107
|
+
end
|
108
|
+
|
109
|
+
def setup_destroy_action(params)
|
110
|
+
parse_remove_operation(params)
|
111
|
+
end
|
112
|
+
|
113
|
+
def setup_destroy_association_action (params)
|
114
|
+
parse_remove_association_operation(params)
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize_source(params)
|
118
|
+
@source_klass = Resource.resource_for(params.require(:source))
|
119
|
+
@source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
|
120
|
+
end
|
121
|
+
|
72
122
|
def parse_pagination(page)
|
73
123
|
paginator_name = @resource_klass._paginator
|
74
124
|
@paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
|
@@ -142,22 +192,29 @@ module JSONAPI
|
|
142
192
|
end
|
143
193
|
end
|
144
194
|
|
145
|
-
def
|
195
|
+
def parse_include_directives(include)
|
146
196
|
return if include.nil?
|
147
197
|
|
148
198
|
included_resources = CSV.parse_line(include)
|
149
199
|
return if included_resources.nil?
|
150
200
|
|
151
|
-
|
201
|
+
include = []
|
152
202
|
included_resources.each do |included_resource|
|
153
203
|
check_include(@resource_klass, included_resource.partition('.'))
|
154
|
-
|
204
|
+
include.push(unformat_key(included_resource).to_s)
|
155
205
|
end
|
206
|
+
|
207
|
+
@include_directives = JSONAPI::IncludeDirectives.new(include)
|
156
208
|
end
|
157
209
|
|
158
210
|
def parse_filters(filters)
|
159
211
|
return unless filters
|
160
|
-
|
212
|
+
|
213
|
+
unless filters.class.method_defined?(:each)
|
214
|
+
@errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
|
215
|
+
return
|
216
|
+
end
|
217
|
+
|
161
218
|
filters.each do |key, value|
|
162
219
|
filter = unformat_key(key)
|
163
220
|
if @resource_klass._allowed_filter?(filter)
|
@@ -168,18 +225,23 @@ module JSONAPI
|
|
168
225
|
end
|
169
226
|
end
|
170
227
|
|
228
|
+
def set_default_filters
|
229
|
+
@resource_klass._allowed_filters.each do |filter, opts|
|
230
|
+
next if opts[:default].nil? || !@filters[filter].nil?
|
231
|
+
@filters[filter] = opts[:default]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
171
235
|
def parse_sort_criteria(sort_criteria)
|
172
236
|
return unless sort_criteria
|
173
237
|
|
174
238
|
@sort_criteria = CSV.parse_line(URI.unescape(sort_criteria)).collect do |sort|
|
175
|
-
|
176
|
-
|
177
|
-
sort_criteria[:direction] = :asc
|
178
|
-
elsif sort.start_with?('-')
|
239
|
+
if sort.start_with?('-')
|
240
|
+
sort_criteria = {field: unformat_key(sort[1..-1]).to_s}
|
179
241
|
sort_criteria[:direction] = :desc
|
180
242
|
else
|
181
|
-
|
182
|
-
|
243
|
+
sort_criteria = {field: unformat_key(sort).to_s}
|
244
|
+
sort_criteria[:direction] = :asc
|
183
245
|
end
|
184
246
|
|
185
247
|
check_sort_criteria(@resource_klass, sort_criteria)
|
@@ -197,27 +259,96 @@ module JSONAPI
|
|
197
259
|
end
|
198
260
|
end
|
199
261
|
|
262
|
+
def add_find_operation
|
263
|
+
@operations.push JSONAPI::FindOperation.new(
|
264
|
+
@resource_klass,
|
265
|
+
{
|
266
|
+
filters: @filters,
|
267
|
+
include_directives: @include_directives,
|
268
|
+
sort_criteria: @sort_criteria,
|
269
|
+
paginator: @paginator
|
270
|
+
}
|
271
|
+
)
|
272
|
+
end
|
273
|
+
|
274
|
+
def add_show_operation
|
275
|
+
@operations.push JSONAPI::ShowOperation.new(
|
276
|
+
@resource_klass,
|
277
|
+
{
|
278
|
+
id: @id,
|
279
|
+
include_directives: @include_directives
|
280
|
+
}
|
281
|
+
)
|
282
|
+
end
|
283
|
+
|
284
|
+
def add_show_association_operation(association_type, parent_key)
|
285
|
+
@operations.push JSONAPI::ShowAssociationOperation.new(
|
286
|
+
@resource_klass,
|
287
|
+
{
|
288
|
+
association_type: association_type,
|
289
|
+
parent_key: @resource_klass.verify_key(parent_key)
|
290
|
+
}
|
291
|
+
)
|
292
|
+
end
|
293
|
+
|
294
|
+
def add_show_related_resource_operation(association_type)
|
295
|
+
@operations.push JSONAPI::ShowRelatedResourceOperation.new(
|
296
|
+
@resource_klass,
|
297
|
+
{
|
298
|
+
association_type: association_type,
|
299
|
+
source_klass: @source_klass,
|
300
|
+
source_id: @source_id
|
301
|
+
}
|
302
|
+
)
|
303
|
+
end
|
304
|
+
|
305
|
+
def add_show_related_resources_operation(association_type)
|
306
|
+
@operations.push JSONAPI::ShowRelatedResourcesOperation.new(
|
307
|
+
@resource_klass,
|
308
|
+
{
|
309
|
+
association_type: association_type,
|
310
|
+
source_klass: @source_klass,
|
311
|
+
source_id: @source_id,
|
312
|
+
filters: @source_klass.verify_filters(@filters, @context),
|
313
|
+
sort_criteria: @sort_criteria,
|
314
|
+
paginator: @paginator
|
315
|
+
}
|
316
|
+
)
|
317
|
+
end
|
318
|
+
|
319
|
+
# TODO: Please remove after `createable_fields` is removed
|
320
|
+
# :nocov:
|
321
|
+
def creatable_fields
|
322
|
+
if @resource_klass.respond_to?(:createable_fields)
|
323
|
+
creatable_fields = @resource_klass.createable_fields(@context)
|
324
|
+
else
|
325
|
+
creatable_fields = @resource_klass.creatable_fields(@context)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
# :nocov:
|
329
|
+
|
200
330
|
def parse_add_operation(data)
|
201
|
-
Array.wrap(data).each do |
|
202
|
-
|
203
|
-
|
331
|
+
Array.wrap(data).each do |params|
|
332
|
+
verify_type(params[:type])
|
333
|
+
|
334
|
+
data = parse_params(params, creatable_fields)
|
335
|
+
@operations.push JSONAPI::CreateResourceOperation.new(
|
336
|
+
@resource_klass,
|
337
|
+
{
|
338
|
+
data: data
|
339
|
+
}
|
340
|
+
)
|
204
341
|
end
|
205
342
|
rescue JSONAPI::Exceptions::Error => e
|
206
343
|
@errors.concat(e.errors)
|
207
344
|
end
|
208
345
|
|
209
|
-
def
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
if params[:type].nil?
|
215
|
-
raise JSONAPI::Exceptions::ParameterMissing.new(:type)
|
216
|
-
else
|
217
|
-
raise JSONAPI::Exceptions::InvalidResource.new(params[:type])
|
218
|
-
end
|
346
|
+
def verify_type(type)
|
347
|
+
if type.nil?
|
348
|
+
raise JSONAPI::Exceptions::ParameterMissing.new(:type)
|
349
|
+
elsif unformat_key(type).to_sym != @resource_klass._type
|
350
|
+
raise JSONAPI::Exceptions::InvalidResource.new(type)
|
219
351
|
end
|
220
|
-
params
|
221
352
|
end
|
222
353
|
|
223
354
|
def parse_has_one_links_object(raw)
|
@@ -264,65 +395,70 @@ module JSONAPI
|
|
264
395
|
checked_has_many_associations = {}
|
265
396
|
|
266
397
|
params.each do |key, value|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
if
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
398
|
+
case key.to_s
|
399
|
+
when 'relationships'
|
400
|
+
value.each do |link_key, link_value|
|
401
|
+
param = unformat_key(link_key)
|
402
|
+
|
403
|
+
association = @resource_klass._association(param)
|
404
|
+
|
405
|
+
if association.is_a?(JSONAPI::Association::HasOne)
|
406
|
+
if link_value.nil?
|
407
|
+
linkage = nil
|
408
|
+
else
|
409
|
+
linkage = link_value[:data]
|
410
|
+
end
|
279
411
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
412
|
+
links_object = parse_has_one_links_object(linkage)
|
413
|
+
# Since we do not yet support polymorphic associations we will raise an error if the type does not match the
|
414
|
+
# association's type.
|
415
|
+
# ToDo: Support Polymorphic associations
|
416
|
+
if links_object[:type] && (links_object[:type].to_s != association.type.to_s)
|
417
|
+
raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
418
|
+
end
|
287
419
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
420
|
+
unless links_object[:id].nil?
|
421
|
+
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
|
422
|
+
checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
|
423
|
+
else
|
424
|
+
checked_has_one_associations[param] = nil
|
425
|
+
end
|
426
|
+
elsif association.is_a?(JSONAPI::Association::HasMany)
|
427
|
+
if link_value.is_a?(Array) && link_value.length == 0
|
428
|
+
linkage = []
|
429
|
+
elsif link_value.is_a?(Hash)
|
430
|
+
linkage = link_value[:data]
|
431
|
+
else
|
432
|
+
raise JSONAPI::Exceptions::InvalidLinksObject.new
|
433
|
+
end
|
302
434
|
|
303
|
-
|
435
|
+
links_object = parse_has_many_links_object(linkage)
|
304
436
|
|
305
|
-
|
306
|
-
|
307
|
-
|
437
|
+
# Since we do not yet support polymorphic associations we will raise an error if the type does not match the
|
438
|
+
# association's type.
|
439
|
+
# ToDo: Support Polymorphic associations
|
308
440
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
441
|
+
if links_object.length == 0
|
442
|
+
checked_has_many_associations[param] = []
|
443
|
+
else
|
444
|
+
if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
|
445
|
+
raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
446
|
+
end
|
315
447
|
|
316
|
-
|
317
|
-
|
318
|
-
|
448
|
+
links_object.each_pair do |type, keys|
|
449
|
+
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
|
450
|
+
checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
|
451
|
+
end
|
319
452
|
end
|
320
453
|
end
|
321
454
|
end
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
455
|
+
when 'id'
|
456
|
+
checked_attributes['id'] = unformat_value(:id, value)
|
457
|
+
when 'attributes'
|
458
|
+
value.each do |key, value|
|
459
|
+
param = unformat_key(key)
|
460
|
+
checked_attributes[param] = unformat_value(param, value)
|
461
|
+
end
|
326
462
|
end
|
327
463
|
end
|
328
464
|
|
@@ -335,35 +471,58 @@ module JSONAPI
|
|
335
471
|
|
336
472
|
def unformat_value(attribute, value)
|
337
473
|
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(@resource_klass._attribute_options(attribute)[:format])
|
338
|
-
value_formatter.unformat(value
|
474
|
+
value_formatter.unformat(value)
|
339
475
|
end
|
340
476
|
|
341
477
|
def verify_permitted_params(params, allowed_fields)
|
342
478
|
formatted_allowed_fields = allowed_fields.collect { |field| format_key(field).to_sym }
|
343
479
|
params_not_allowed = []
|
480
|
+
|
344
481
|
params.each do |key, value|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
482
|
+
case key.to_s
|
483
|
+
when 'relationships'
|
484
|
+
value.each_key do |links_key|
|
485
|
+
params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
|
486
|
+
end
|
487
|
+
when 'attributes'
|
488
|
+
value.each do |attr_key, attr_value|
|
489
|
+
params_not_allowed.push(attr_key) unless formatted_allowed_fields.include?(attr_key.to_sym)
|
490
|
+
end
|
491
|
+
when 'type', 'id'
|
492
|
+
else
|
493
|
+
params_not_allowed.push(key)
|
351
494
|
end
|
352
495
|
end
|
496
|
+
|
353
497
|
raise JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
|
354
498
|
end
|
355
499
|
|
500
|
+
# TODO: Please remove after `updateable_fields` is removed
|
501
|
+
# :nocov:
|
502
|
+
def updatable_fields
|
503
|
+
if @resource_klass.respond_to?(:updateable_fields)
|
504
|
+
@resource_klass.updateable_fields(@context)
|
505
|
+
else
|
506
|
+
@resource_klass.updatable_fields(@context)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
# :nocov:
|
510
|
+
|
356
511
|
def parse_add_association_operation(data, association_type, parent_key)
|
357
512
|
association = resource_klass._association(association_type)
|
358
513
|
|
359
514
|
if association.is_a?(JSONAPI::Association::HasMany)
|
360
|
-
object_params = {
|
361
|
-
verified_param_set = parse_params(object_params,
|
362
|
-
|
363
|
-
@operations.push JSONAPI::CreateHasManyAssociationOperation.new(
|
364
|
-
|
365
|
-
|
366
|
-
|
515
|
+
object_params = {relationships: {format_key(association.name) => {data: data}}}
|
516
|
+
verified_param_set = parse_params(object_params, updatable_fields)
|
517
|
+
|
518
|
+
@operations.push JSONAPI::CreateHasManyAssociationOperation.new(
|
519
|
+
resource_klass,
|
520
|
+
{
|
521
|
+
resource_id: parent_key,
|
522
|
+
association_type: association_type,
|
523
|
+
data: verified_param_set[:has_many].values[0]
|
524
|
+
}
|
525
|
+
)
|
367
526
|
end
|
368
527
|
end
|
369
528
|
|
@@ -371,31 +530,38 @@ module JSONAPI
|
|
371
530
|
association = resource_klass._association(association_type)
|
372
531
|
|
373
532
|
if association.is_a?(JSONAPI::Association::HasOne)
|
374
|
-
object_params = {
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
533
|
+
object_params = {relationships: {format_key(association.name) => {data: data}}}
|
534
|
+
verified_param_set = parse_params(object_params, updatable_fields)
|
535
|
+
|
536
|
+
@operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(
|
537
|
+
resource_klass,
|
538
|
+
{
|
539
|
+
resource_id: parent_key,
|
540
|
+
association_type: association_type,
|
541
|
+
key_value: verified_param_set[:has_one].values[0]
|
542
|
+
}
|
543
|
+
)
|
382
544
|
else
|
383
545
|
unless association.acts_as_set
|
384
546
|
raise JSONAPI::Exceptions::HasManySetReplacementForbidden.new
|
385
547
|
end
|
386
548
|
|
387
|
-
object_params = {
|
388
|
-
verified_param_set = parse_params(object_params,
|
389
|
-
|
390
|
-
@operations.push JSONAPI::ReplaceHasManyAssociationOperation.new(
|
391
|
-
|
392
|
-
|
393
|
-
|
549
|
+
object_params = {relationships: {format_key(association.name) => {data: data}}}
|
550
|
+
verified_param_set = parse_params(object_params, updatable_fields)
|
551
|
+
|
552
|
+
@operations.push JSONAPI::ReplaceHasManyAssociationOperation.new(
|
553
|
+
resource_klass,
|
554
|
+
{
|
555
|
+
resource_id: parent_key,
|
556
|
+
association_type: association_type,
|
557
|
+
data: verified_param_set[:has_many].values[0]
|
558
|
+
}
|
559
|
+
)
|
394
560
|
end
|
395
561
|
end
|
396
562
|
|
397
563
|
def parse_single_replace_operation(data, keys)
|
398
|
-
if data[
|
564
|
+
if data[:id].nil?
|
399
565
|
raise JSONAPI::Exceptions::MissingKey.new
|
400
566
|
end
|
401
567
|
|
@@ -404,19 +570,24 @@ module JSONAPI
|
|
404
570
|
raise JSONAPI::Exceptions::ParameterMissing.new(:type)
|
405
571
|
end
|
406
572
|
|
407
|
-
key = data[
|
573
|
+
key = data[:id]
|
408
574
|
if !keys.include?(key)
|
409
575
|
raise JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
|
410
576
|
end
|
411
577
|
|
412
|
-
if !keys.include?(
|
578
|
+
if !keys.include?(:id)
|
413
579
|
data.delete(:id)
|
414
580
|
end
|
415
581
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
582
|
+
verify_type(data[:type])
|
583
|
+
|
584
|
+
@operations.push JSONAPI::ReplaceFieldsOperation.new(
|
585
|
+
@resource_klass,
|
586
|
+
{
|
587
|
+
resource_id: key,
|
588
|
+
data: parse_params(data, updatable_fields)
|
589
|
+
}
|
590
|
+
)
|
420
591
|
end
|
421
592
|
|
422
593
|
def parse_replace_operation(data, keys)
|
@@ -437,10 +608,15 @@ module JSONAPI
|
|
437
608
|
end
|
438
609
|
|
439
610
|
def parse_remove_operation(params)
|
440
|
-
keys = parse_key_array(params.permit(
|
611
|
+
keys = parse_key_array(params.permit(:id)[:id])
|
441
612
|
|
442
613
|
keys.each do |key|
|
443
|
-
@operations.push JSONAPI::RemoveResourceOperation.new(
|
614
|
+
@operations.push JSONAPI::RemoveResourceOperation.new(
|
615
|
+
@resource_klass,
|
616
|
+
{
|
617
|
+
resource_id: key
|
618
|
+
}
|
619
|
+
)
|
444
620
|
end
|
445
621
|
rescue ActionController::UnpermittedParameters => e
|
446
622
|
@errors.concat(JSONAPI::Exceptions::ParametersNotAllowed.new(e.params).errors)
|
@@ -457,15 +633,23 @@ module JSONAPI
|
|
457
633
|
if association.is_a?(JSONAPI::Association::HasMany)
|
458
634
|
keys = parse_key_array(params[:keys])
|
459
635
|
keys.each do |key|
|
460
|
-
@operations.push JSONAPI::RemoveHasManyAssociationOperation.new(
|
461
|
-
|
462
|
-
|
463
|
-
|
636
|
+
@operations.push JSONAPI::RemoveHasManyAssociationOperation.new(
|
637
|
+
resource_klass,
|
638
|
+
{
|
639
|
+
resource_id: parent_key,
|
640
|
+
association_type: association_type,
|
641
|
+
associated_key: key
|
642
|
+
}
|
643
|
+
)
|
464
644
|
end
|
465
645
|
else
|
466
|
-
@operations.push JSONAPI::RemoveHasOneAssociationOperation.new(
|
467
|
-
|
468
|
-
|
646
|
+
@operations.push JSONAPI::RemoveHasOneAssociationOperation.new(
|
647
|
+
resource_klass,
|
648
|
+
{
|
649
|
+
resource_id: parent_key,
|
650
|
+
association_type: association_type
|
651
|
+
}
|
652
|
+
)
|
469
653
|
end
|
470
654
|
end
|
471
655
|
|