jsonapi-resources 0.3.3 → 0.4.0
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/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
|
|