jsonapi-resources 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/README.md +103 -71
- data/Rakefile +2 -2
- data/jsonapi-resources.gemspec +2 -2
- data/lib/jsonapi-resources.rb +0 -1
- data/lib/jsonapi/active_record_operations_processor.rb +10 -2
- data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
- data/lib/jsonapi/association.rb +50 -15
- data/lib/jsonapi/callbacks.rb +1 -2
- data/lib/jsonapi/configuration.rb +8 -24
- data/lib/jsonapi/error.rb +1 -2
- data/lib/jsonapi/error_codes.rb +3 -1
- data/lib/jsonapi/exceptions.rb +59 -47
- data/lib/jsonapi/include_directives.rb +11 -11
- data/lib/jsonapi/mime_types.rb +2 -2
- data/lib/jsonapi/operation.rb +28 -11
- data/lib/jsonapi/operations_processor.rb +16 -5
- data/lib/jsonapi/paginator.rb +19 -19
- data/lib/jsonapi/request.rb +175 -196
- data/lib/jsonapi/resource.rb +158 -105
- data/lib/jsonapi/resource_serializer.rb +37 -26
- data/lib/jsonapi/resources/version.rb +2 -2
- data/lib/jsonapi/response_document.rb +5 -4
- data/lib/jsonapi/routing_ext.rb +24 -19
- data/test/controllers/controller_test.rb +261 -31
- data/test/fixtures/active_record.rb +206 -8
- data/test/fixtures/book_comments.yml +2 -1
- data/test/fixtures/books.yml +1 -0
- data/test/fixtures/documents.yml +3 -0
- data/test/fixtures/people.yml +8 -1
- data/test/fixtures/pictures.yml +15 -0
- data/test/fixtures/products.yml +3 -0
- data/test/fixtures/vehicles.yml +8 -0
- data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
- data/test/integration/requests/request_test.rb +14 -3
- data/test/integration/routes/routes_test.rb +47 -0
- data/test/test_helper.rb +27 -4
- data/test/unit/serializer/include_directives_test.rb +5 -0
- data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
- data/test/unit/serializer/serializer_test.rb +19 -1
- metadata +14 -4
@@ -1,18 +1,17 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
class IncludeDirectives
|
3
|
-
|
4
3
|
# Construct an IncludeDirectives Hash from an array of dot separated include strings.
|
5
4
|
# For example ['posts.comments.tags']
|
6
5
|
# will transform into =>
|
7
6
|
# {
|
8
|
-
# :
|
9
|
-
# :
|
10
|
-
# :
|
11
|
-
# :
|
12
|
-
# :
|
13
|
-
# :
|
14
|
-
# :
|
15
|
-
# :
|
7
|
+
# posts:{
|
8
|
+
# include:true,
|
9
|
+
# include_related:{
|
10
|
+
# comments:{
|
11
|
+
# include:true,
|
12
|
+
# include_related:{
|
13
|
+
# tags:{
|
14
|
+
# include:true
|
16
15
|
# }
|
17
16
|
# }
|
18
17
|
# }
|
@@ -21,7 +20,7 @@ module JSONAPI
|
|
21
20
|
# }
|
22
21
|
|
23
22
|
def initialize(includes_array)
|
24
|
-
@include_directives_hash = {include_related: {}}
|
23
|
+
@include_directives_hash = { include_related: {} }
|
25
24
|
includes_array.each do |include|
|
26
25
|
parse_include(include)
|
27
26
|
end
|
@@ -36,11 +35,12 @@ module JSONAPI
|
|
36
35
|
end
|
37
36
|
|
38
37
|
private
|
38
|
+
|
39
39
|
def get_related(current_path)
|
40
40
|
current = @include_directives_hash
|
41
41
|
current_path.split('.').each do |fragment|
|
42
42
|
fragment = fragment.to_sym
|
43
|
-
current[:include_related][fragment] ||= {include: false, include_related: {}}
|
43
|
+
current[:include_related][fragment] ||= { include: false, include_related: {} }
|
44
44
|
current = current[:include_related][fragment]
|
45
45
|
end
|
46
46
|
current
|
data/lib/jsonapi/mime_types.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module JSONAPI
|
2
|
-
MEDIA_TYPE =
|
2
|
+
MEDIA_TYPE = 'application/vnd.api+json'
|
3
3
|
end
|
4
4
|
|
5
5
|
Mime::Type.register JSONAPI::MEDIA_TYPE, :api_json
|
6
6
|
|
7
|
-
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MEDIA_TYPE)]=lambda do |body|
|
7
|
+
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MEDIA_TYPE)] = lambda do |body|
|
8
8
|
JSON.parse(body)
|
9
9
|
end
|
data/lib/jsonapi/operation.rb
CHANGED
@@ -27,8 +27,8 @@ module JSONAPI
|
|
27
27
|
|
28
28
|
def record_count
|
29
29
|
@_record_count ||= @resource_klass.find_count(@resource_klass.verify_filters(@filters, @context),
|
30
|
-
|
31
|
-
|
30
|
+
context: @context,
|
31
|
+
include_directives: @include_directives)
|
32
32
|
end
|
33
33
|
|
34
34
|
def pagination_params
|
@@ -43,10 +43,10 @@ module JSONAPI
|
|
43
43
|
|
44
44
|
def apply
|
45
45
|
resource_records = @resource_klass.find(@resource_klass.verify_filters(@filters, @context),
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
context: @context,
|
47
|
+
include_directives: @include_directives,
|
48
|
+
sort_criteria: @sort_criteria,
|
49
|
+
paginator: @paginator)
|
50
50
|
|
51
51
|
options = {}
|
52
52
|
if JSONAPI.configuration.top_level_links_include_pagination
|
@@ -153,11 +153,9 @@ module JSONAPI
|
|
153
153
|
source_resource = @source_klass.find_by_key(@source_id, context: @context)
|
154
154
|
|
155
155
|
related_resource = source_resource.send(@association_type,
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
paginator: @paginator
|
160
|
-
})
|
156
|
+
filters: @filters,
|
157
|
+
sort_criteria: @sort_criteria,
|
158
|
+
paginator: @paginator)
|
161
159
|
|
162
160
|
return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
|
163
161
|
|
@@ -238,6 +236,25 @@ module JSONAPI
|
|
238
236
|
end
|
239
237
|
end
|
240
238
|
|
239
|
+
class ReplacePolymorphicHasOneAssociationOperation < Operation
|
240
|
+
attr_reader :resource_id, :association_type, :key_value, :key_type
|
241
|
+
|
242
|
+
def initialize(resource_klass, options = {})
|
243
|
+
@resource_id = options.fetch(:resource_id)
|
244
|
+
@key_value = options.fetch(:key_value)
|
245
|
+
@key_type = options.fetch(:key_type)
|
246
|
+
@association_type = options.fetch(:association_type).to_sym
|
247
|
+
super(resource_klass, options)
|
248
|
+
end
|
249
|
+
|
250
|
+
def apply
|
251
|
+
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
252
|
+
result = resource.replace_polymorphic_has_one_link(@association_type, @key_value, @key_type)
|
253
|
+
|
254
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
241
258
|
class CreateHasManyAssociationOperation < Operation
|
242
259
|
attr_reader :resource_id, :association_type, :data
|
243
260
|
|
@@ -12,6 +12,7 @@ module JSONAPI
|
|
12
12
|
:remove_resource_operation,
|
13
13
|
:replace_fields_operation,
|
14
14
|
:replace_has_one_association_operation,
|
15
|
+
:replace_polymorphic_has_one_association_operation,
|
15
16
|
:create_has_many_association_operation,
|
16
17
|
:replace_has_many_association_operation,
|
17
18
|
:remove_has_many_association_operation,
|
@@ -34,7 +35,7 @@ module JSONAPI
|
|
34
35
|
@transactional = false
|
35
36
|
if @operations.length > 1
|
36
37
|
@operations.each do |operation|
|
37
|
-
@transactional
|
38
|
+
@transactional |= operation.transactional
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -56,9 +57,7 @@ module JSONAPI
|
|
56
57
|
@result.meta.merge!(@operation_meta)
|
57
58
|
@result.links.merge!(@operation_links)
|
58
59
|
@results.add_result(@result)
|
59
|
-
if @results.has_errors?
|
60
|
-
rollback
|
61
|
-
end
|
60
|
+
rollback if @results.has_errors?
|
62
61
|
end
|
63
62
|
end
|
64
63
|
@results.meta = @operations_meta
|
@@ -82,9 +81,21 @@ module JSONAPI
|
|
82
81
|
|
83
82
|
def process_operation(operation)
|
84
83
|
operation.apply
|
84
|
+
|
85
|
+
rescue JSONAPI::Exceptions::Error => e
|
86
|
+
# :nocov:
|
87
|
+
raise e
|
88
|
+
# :nocov:
|
89
|
+
|
90
|
+
rescue => e
|
91
|
+
# :nocov:
|
92
|
+
internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
|
93
|
+
Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
|
94
|
+
return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
|
95
|
+
# :nocov:
|
85
96
|
end
|
86
97
|
end
|
87
98
|
end
|
88
99
|
|
89
100
|
class BasicOperationsProcessor < JSONAPI::OperationsProcessor
|
90
|
-
end
|
101
|
+
end
|
data/lib/jsonapi/paginator.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
class Paginator
|
3
|
-
def initialize(
|
3
|
+
def initialize(_params)
|
4
4
|
end
|
5
5
|
|
6
|
-
def apply(
|
6
|
+
def apply(_relation, _order_options)
|
7
7
|
# relation
|
8
8
|
end
|
9
9
|
|
10
|
-
def links_page_params(
|
10
|
+
def links_page_params(_options = {})
|
11
11
|
# :nocov:
|
12
12
|
{}
|
13
13
|
# :nocov:
|
@@ -40,7 +40,7 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
40
40
|
true
|
41
41
|
end
|
42
42
|
|
43
|
-
def apply(relation,
|
43
|
+
def apply(relation, _order_options)
|
44
44
|
relation.offset(@offset).limit(@limit)
|
45
45
|
end
|
46
46
|
|
@@ -56,9 +56,7 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
56
56
|
if @offset > 0
|
57
57
|
previous_offset = @offset - @limit
|
58
58
|
|
59
|
-
if previous_offset < 0
|
60
|
-
previous_offset = 0
|
61
|
-
end
|
59
|
+
previous_offset = 0 if previous_offset < 0
|
62
60
|
|
63
61
|
links_page_params['previous'] = {
|
64
62
|
'offset' => previous_offset,
|
@@ -71,7 +69,7 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
71
69
|
unless next_offset >= record_count
|
72
70
|
links_page_params['next'] = {
|
73
71
|
'offset' => next_offset,
|
74
|
-
'limit'=> @limit
|
72
|
+
'limit' => @limit
|
75
73
|
}
|
76
74
|
end
|
77
75
|
|
@@ -86,6 +84,7 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
86
84
|
end
|
87
85
|
|
88
86
|
private
|
87
|
+
|
89
88
|
def parse_pagination_params(params)
|
90
89
|
if params.nil?
|
91
90
|
@offset = 0
|
@@ -96,7 +95,7 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
96
95
|
@offset = validparams[:offset] ? validparams[:offset].to_i : 0
|
97
96
|
@limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
|
98
97
|
else
|
99
|
-
|
98
|
+
fail JSONAPI::Exceptions::InvalidPageObject.new
|
100
99
|
end
|
101
100
|
rescue ActionController::UnpermittedParameters => e
|
102
101
|
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
@@ -104,14 +103,14 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
104
103
|
|
105
104
|
def verify_pagination_params
|
106
105
|
if @limit < 1
|
107
|
-
|
106
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
|
108
107
|
elsif @limit > JSONAPI.configuration.maximum_page_size
|
109
|
-
|
110
|
-
|
108
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
|
109
|
+
"Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
111
110
|
end
|
112
111
|
|
113
112
|
if @offset < 0
|
114
|
-
|
113
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
|
115
114
|
end
|
116
115
|
end
|
117
116
|
end
|
@@ -128,7 +127,7 @@ class PagedPaginator < JSONAPI::Paginator
|
|
128
127
|
true
|
129
128
|
end
|
130
129
|
|
131
|
-
def apply(relation,
|
130
|
+
def apply(relation, _order_options)
|
132
131
|
offset = (@number - 1) * @size
|
133
132
|
relation.offset(offset).limit(@size)
|
134
133
|
end
|
@@ -154,7 +153,7 @@ class PagedPaginator < JSONAPI::Paginator
|
|
154
153
|
unless @number >= page_count
|
155
154
|
links_page_params['next'] = {
|
156
155
|
'number' => @number + 1,
|
157
|
-
'size'=> @size
|
156
|
+
'size' => @size
|
158
157
|
}
|
159
158
|
end
|
160
159
|
|
@@ -169,6 +168,7 @@ class PagedPaginator < JSONAPI::Paginator
|
|
169
168
|
end
|
170
169
|
|
171
170
|
private
|
171
|
+
|
172
172
|
def parse_pagination_params(params)
|
173
173
|
if params.nil?
|
174
174
|
@number = 1
|
@@ -188,14 +188,14 @@ class PagedPaginator < JSONAPI::Paginator
|
|
188
188
|
|
189
189
|
def verify_pagination_params
|
190
190
|
if @size < 1
|
191
|
-
|
191
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
|
192
192
|
elsif @size > JSONAPI.configuration.maximum_page_size
|
193
|
-
|
194
|
-
|
193
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
|
194
|
+
"size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
195
195
|
end
|
196
196
|
|
197
197
|
if @number < 1
|
198
|
-
|
198
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
|
199
199
|
end
|
200
200
|
end
|
201
201
|
end
|
data/lib/jsonapi/request.rb
CHANGED
@@ -15,7 +15,7 @@ module JSONAPI
|
|
15
15
|
@operations = []
|
16
16
|
@fields = {}
|
17
17
|
@filters = {}
|
18
|
-
@sort_criteria = [{field: 'id', direction: :asc}]
|
18
|
+
@sort_criteria = [{ field: 'id', direction: :asc }]
|
19
19
|
@source_klass = nil
|
20
20
|
@source_id = nil
|
21
21
|
@include_directives = nil
|
@@ -111,7 +111,7 @@ module JSONAPI
|
|
111
111
|
parse_remove_operation(params)
|
112
112
|
end
|
113
113
|
|
114
|
-
def setup_destroy_association_action
|
114
|
+
def setup_destroy_association_action(params)
|
115
115
|
parse_remove_association_operation(params)
|
116
116
|
end
|
117
117
|
|
@@ -139,7 +139,7 @@ module JSONAPI
|
|
139
139
|
extracted_fields[field] = resource_fields
|
140
140
|
end
|
141
141
|
else
|
142
|
-
|
142
|
+
fail JSONAPI::Exceptions::InvalidFieldFormat.new
|
143
143
|
end
|
144
144
|
|
145
145
|
# Validate the fields
|
@@ -148,13 +148,13 @@ module JSONAPI
|
|
148
148
|
extracted_fields[type] = []
|
149
149
|
begin
|
150
150
|
if type != format_key(type)
|
151
|
-
|
151
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
152
152
|
end
|
153
153
|
type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
|
154
154
|
rescue NameError
|
155
155
|
@errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
|
156
156
|
rescue JSONAPI::Exceptions::InvalidResource => e
|
157
|
-
|
157
|
+
@errors.concat(e.errors)
|
158
158
|
end
|
159
159
|
|
160
160
|
if type_resource.nil? || !(@resource_klass._type == underscored_type ||
|
@@ -185,11 +185,11 @@ module JSONAPI
|
|
185
185
|
association = resource_klass._association(association_name)
|
186
186
|
if association && format_key(association_name) == include_parts.first
|
187
187
|
unless include_parts.last.empty?
|
188
|
-
check_include(Resource.resource_for(@resource_klass.module_path + association.class_name.to_s), include_parts.last.partition('.'))
|
188
|
+
check_include(Resource.resource_for(@resource_klass.module_path + association.class_name.to_s.underscore), include_parts.last.partition('.'))
|
189
189
|
end
|
190
190
|
else
|
191
191
|
@errors.concat(JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type),
|
192
|
-
include_parts.first
|
192
|
+
include_parts.first).errors)
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
@@ -238,10 +238,10 @@ module JSONAPI
|
|
238
238
|
|
239
239
|
@sort_criteria = CSV.parse_line(URI.unescape(sort_criteria)).collect do |sort|
|
240
240
|
if sort.start_with?('-')
|
241
|
-
sort_criteria = {field: unformat_key(sort[1..-1]).to_s}
|
241
|
+
sort_criteria = { field: unformat_key(sort[1..-1]).to_s }
|
242
242
|
sort_criteria[:direction] = :desc
|
243
243
|
else
|
244
|
-
sort_criteria = {field: unformat_key(sort).to_s}
|
244
|
+
sort_criteria = { field: unformat_key(sort).to_s }
|
245
245
|
sort_criteria[:direction] = :asc
|
246
246
|
end
|
247
247
|
|
@@ -262,64 +262,54 @@ module JSONAPI
|
|
262
262
|
|
263
263
|
def add_find_operation
|
264
264
|
@operations.push JSONAPI::FindOperation.new(
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
}
|
273
|
-
)
|
265
|
+
@resource_klass,
|
266
|
+
context: @context,
|
267
|
+
filters: @filters,
|
268
|
+
include_directives: @include_directives,
|
269
|
+
sort_criteria: @sort_criteria,
|
270
|
+
paginator: @paginator
|
271
|
+
)
|
274
272
|
end
|
275
273
|
|
276
274
|
def add_show_operation
|
277
275
|
@operations.push JSONAPI::ShowOperation.new(
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
}
|
284
|
-
)
|
276
|
+
@resource_klass,
|
277
|
+
context: @context,
|
278
|
+
id: @id,
|
279
|
+
include_directives: @include_directives
|
280
|
+
)
|
285
281
|
end
|
286
282
|
|
287
283
|
def add_show_association_operation(association_type, parent_key)
|
288
284
|
@operations.push JSONAPI::ShowAssociationOperation.new(
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
}
|
295
|
-
)
|
285
|
+
@resource_klass,
|
286
|
+
context: @context,
|
287
|
+
association_type: association_type,
|
288
|
+
parent_key: @resource_klass.verify_key(parent_key)
|
289
|
+
)
|
296
290
|
end
|
297
291
|
|
298
292
|
def add_show_related_resource_operation(association_type)
|
299
293
|
@operations.push JSONAPI::ShowRelatedResourceOperation.new(
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
}
|
307
|
-
)
|
294
|
+
@resource_klass,
|
295
|
+
context: @context,
|
296
|
+
association_type: association_type,
|
297
|
+
source_klass: @source_klass,
|
298
|
+
source_id: @source_id
|
299
|
+
)
|
308
300
|
end
|
309
301
|
|
310
302
|
def add_show_related_resources_operation(association_type)
|
311
303
|
@operations.push JSONAPI::ShowRelatedResourcesOperation.new(
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
}
|
322
|
-
)
|
304
|
+
@resource_klass,
|
305
|
+
context: @context,
|
306
|
+
association_type: association_type,
|
307
|
+
source_klass: @source_klass,
|
308
|
+
source_id: @source_id,
|
309
|
+
filters: @source_klass.verify_filters(@filters, @context),
|
310
|
+
sort_criteria: @sort_criteria,
|
311
|
+
paginator: @paginator
|
312
|
+
)
|
323
313
|
end
|
324
314
|
|
325
315
|
# TODO: Please remove after `createable_fields` is removed
|
@@ -339,12 +329,10 @@ module JSONAPI
|
|
339
329
|
|
340
330
|
data = parse_params(params, creatable_fields)
|
341
331
|
@operations.push JSONAPI::CreateResourceOperation.new(
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
}
|
347
|
-
)
|
332
|
+
@resource_klass,
|
333
|
+
context: @context,
|
334
|
+
data: data
|
335
|
+
)
|
348
336
|
end
|
349
337
|
rescue JSONAPI::Exceptions::Error => e
|
350
338
|
@errors.concat(e.errors)
|
@@ -352,9 +340,9 @@ module JSONAPI
|
|
352
340
|
|
353
341
|
def verify_type(type)
|
354
342
|
if type.nil?
|
355
|
-
|
343
|
+
fail JSONAPI::Exceptions::ParameterMissing.new(:type)
|
356
344
|
elsif unformat_key(type).to_sym != @resource_klass._type
|
357
|
-
|
345
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
358
346
|
end
|
359
347
|
end
|
360
348
|
|
@@ -366,8 +354,8 @@ module JSONAPI
|
|
366
354
|
}
|
367
355
|
end
|
368
356
|
|
369
|
-
if !raw.is_a?(Hash) || raw.length != 2 || !(raw.
|
370
|
-
|
357
|
+
if !raw.is_a?(Hash) || raw.length != 2 || !(raw.key?('type') && raw.key?('id'))
|
358
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
371
359
|
end
|
372
360
|
|
373
361
|
{
|
@@ -377,9 +365,7 @@ module JSONAPI
|
|
377
365
|
end
|
378
366
|
|
379
367
|
def parse_has_many_links_object(raw)
|
380
|
-
if raw.nil?
|
381
|
-
raise JSONAPI::Exceptions::InvalidLinksObject.new
|
382
|
-
end
|
368
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
|
383
369
|
|
384
370
|
links_object = {}
|
385
371
|
if raw.is_a?(Array)
|
@@ -389,7 +375,7 @@ module JSONAPI
|
|
389
375
|
links_object[link_object[:type]].push(link_object[:id])
|
390
376
|
end
|
391
377
|
else
|
392
|
-
|
378
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
393
379
|
end
|
394
380
|
links_object
|
395
381
|
end
|
@@ -403,69 +389,69 @@ module JSONAPI
|
|
403
389
|
|
404
390
|
params.each do |key, value|
|
405
391
|
case key.to_s
|
406
|
-
|
407
|
-
|
408
|
-
|
392
|
+
when 'relationships'
|
393
|
+
value.each do |link_key, link_value|
|
394
|
+
param = unformat_key(link_key)
|
395
|
+
association = @resource_klass._association(param)
|
396
|
+
if association.is_a?(JSONAPI::Association::HasOne)
|
397
|
+
if link_value.nil?
|
398
|
+
linkage = nil
|
399
|
+
else
|
400
|
+
linkage = link_value[:data]
|
401
|
+
end
|
409
402
|
|
410
|
-
|
403
|
+
links_object = parse_has_one_links_object(linkage)
|
404
|
+
if !association.polymorphic? && links_object[:type] && (links_object[:type].to_s != association.type.to_s)
|
405
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
406
|
+
end
|
411
407
|
|
412
|
-
|
413
|
-
|
414
|
-
|
408
|
+
unless links_object[:id].nil?
|
409
|
+
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
|
410
|
+
association_id = association_resource.verify_key(links_object[:id], @context)
|
411
|
+
if association.polymorphic?
|
412
|
+
checked_has_one_associations[param] = { id: association_id, type: unformat_key(links_object[:type].to_s) }
|
415
413
|
else
|
416
|
-
|
417
|
-
end
|
418
|
-
|
419
|
-
links_object = parse_has_one_links_object(linkage)
|
420
|
-
# Since we do not yet support polymorphic associations we will raise an error if the type does not match the
|
421
|
-
# association's type.
|
422
|
-
# ToDo: Support Polymorphic associations
|
423
|
-
if links_object[:type] && (links_object[:type].to_s != association.type.to_s)
|
424
|
-
raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
414
|
+
checked_has_one_associations[param] = association_id
|
425
415
|
end
|
416
|
+
else
|
417
|
+
checked_has_one_associations[param] = nil
|
418
|
+
end
|
419
|
+
elsif association.is_a?(JSONAPI::Association::HasMany)
|
420
|
+
if link_value.is_a?(Array) && link_value.length == 0
|
421
|
+
linkage = []
|
422
|
+
elsif link_value.is_a?(Hash)
|
423
|
+
linkage = link_value[:data]
|
424
|
+
else
|
425
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
426
|
+
end
|
426
427
|
|
427
|
-
|
428
|
-
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
|
429
|
-
checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
|
430
|
-
else
|
431
|
-
checked_has_one_associations[param] = nil
|
432
|
-
end
|
433
|
-
elsif association.is_a?(JSONAPI::Association::HasMany)
|
434
|
-
if link_value.is_a?(Array) && link_value.length == 0
|
435
|
-
linkage = []
|
436
|
-
elsif link_value.is_a?(Hash)
|
437
|
-
linkage = link_value[:data]
|
438
|
-
else
|
439
|
-
raise JSONAPI::Exceptions::InvalidLinksObject.new
|
440
|
-
end
|
428
|
+
links_object = parse_has_many_links_object(linkage)
|
441
429
|
|
442
|
-
|
430
|
+
# Since we do not yet support polymorphic associations we will raise an error if the type does not match the
|
431
|
+
# association's type.
|
432
|
+
# ToDo: Support Polymorphic associations
|
443
433
|
|
444
|
-
|
445
|
-
|
446
|
-
|
434
|
+
if links_object.length == 0
|
435
|
+
checked_has_many_associations[param] = []
|
436
|
+
else
|
437
|
+
if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
|
438
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
439
|
+
end
|
447
440
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
|
452
|
-
raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
453
|
-
end
|
454
|
-
|
455
|
-
links_object.each_pair do |type, keys|
|
456
|
-
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
|
457
|
-
checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
|
458
|
-
end
|
441
|
+
links_object.each_pair do |type, keys|
|
442
|
+
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
|
443
|
+
checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
|
459
444
|
end
|
460
445
|
end
|
461
446
|
end
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
447
|
+
end
|
448
|
+
when 'id'
|
449
|
+
checked_attributes['id'] = unformat_value(:id, value)
|
450
|
+
when 'attributes'
|
451
|
+
value.each do |key, value|
|
452
|
+
param = unformat_key(key)
|
453
|
+
checked_attributes[param] = unformat_value(param, value)
|
454
|
+
end
|
469
455
|
end
|
470
456
|
end
|
471
457
|
|
@@ -487,21 +473,21 @@ module JSONAPI
|
|
487
473
|
|
488
474
|
params.each do |key, value|
|
489
475
|
case key.to_s
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
476
|
+
when 'relationships'
|
477
|
+
value.each_key do |links_key|
|
478
|
+
params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
|
479
|
+
end
|
480
|
+
when 'attributes'
|
481
|
+
value.each do |attr_key, _attr_value|
|
482
|
+
params_not_allowed.push(attr_key) unless formatted_allowed_fields.include?(attr_key.to_sym)
|
483
|
+
end
|
484
|
+
when 'type', 'id'
|
485
|
+
else
|
486
|
+
params_not_allowed.push(key)
|
501
487
|
end
|
502
488
|
end
|
503
489
|
|
504
|
-
|
490
|
+
fail JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
|
505
491
|
end
|
506
492
|
|
507
493
|
# TODO: Please remove after `updateable_fields` is removed
|
@@ -519,93 +505,92 @@ module JSONAPI
|
|
519
505
|
association = resource_klass._association(association_type)
|
520
506
|
|
521
507
|
if association.is_a?(JSONAPI::Association::HasMany)
|
522
|
-
object_params = {relationships: {format_key(association.name) => {data: data}}}
|
508
|
+
object_params = { relationships: { format_key(association.name) => { data: data } } }
|
523
509
|
verified_param_set = parse_params(object_params, updatable_fields)
|
524
510
|
|
525
511
|
@operations.push JSONAPI::CreateHasManyAssociationOperation.new(
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
}
|
533
|
-
)
|
512
|
+
resource_klass,
|
513
|
+
context: @context,
|
514
|
+
resource_id: parent_key,
|
515
|
+
association_type: association_type,
|
516
|
+
data: verified_param_set[:has_many].values[0]
|
517
|
+
)
|
534
518
|
end
|
535
519
|
end
|
536
520
|
|
537
521
|
def parse_update_association_operation(data, association_type, parent_key)
|
538
522
|
association = resource_klass._association(association_type)
|
539
|
-
|
540
523
|
if association.is_a?(JSONAPI::Association::HasOne)
|
541
|
-
|
542
|
-
|
524
|
+
if association.polymorphic?
|
525
|
+
object_params = { relationships: { format_key(association.name) => { data: data } } }
|
526
|
+
verified_param_set = parse_params(object_params, updatable_fields)
|
543
527
|
|
544
|
-
|
545
|
-
|
546
|
-
|
528
|
+
@operations.push JSONAPI::ReplacePolymorphicHasOneAssociationOperation.new(
|
529
|
+
resource_klass,
|
530
|
+
context: @context,
|
531
|
+
resource_id: parent_key,
|
532
|
+
association_type: association_type,
|
533
|
+
key_value: verified_param_set[:has_one].values[0][:id],
|
534
|
+
key_type: verified_param_set[:has_one].values[0][:type]
|
535
|
+
)
|
536
|
+
else
|
537
|
+
object_params = { relationships: { format_key(association.name) => { data: data } } }
|
538
|
+
verified_param_set = parse_params(object_params, updatable_fields)
|
539
|
+
|
540
|
+
@operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(
|
541
|
+
resource_klass,
|
547
542
|
context: @context,
|
548
543
|
resource_id: parent_key,
|
549
544
|
association_type: association_type,
|
550
545
|
key_value: verified_param_set[:has_one].values[0]
|
551
|
-
|
552
|
-
|
553
|
-
|
546
|
+
)
|
547
|
+
end
|
548
|
+
elsif association.is_a?(JSONAPI::Association::HasMany)
|
554
549
|
unless association.acts_as_set
|
555
|
-
|
550
|
+
fail JSONAPI::Exceptions::HasManySetReplacementForbidden.new
|
556
551
|
end
|
557
552
|
|
558
|
-
object_params = {relationships: {format_key(association.name) => {data: data}}}
|
553
|
+
object_params = { relationships: { format_key(association.name) => { data: data } } }
|
559
554
|
verified_param_set = parse_params(object_params, updatable_fields)
|
560
555
|
|
561
556
|
@operations.push JSONAPI::ReplaceHasManyAssociationOperation.new(
|
562
557
|
resource_klass,
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
data: verified_param_set[:has_many].values[0]
|
568
|
-
}
|
558
|
+
context: @context,
|
559
|
+
resource_id: parent_key,
|
560
|
+
association_type: association_type,
|
561
|
+
data: verified_param_set[:has_many].values[0]
|
569
562
|
)
|
570
563
|
end
|
571
564
|
end
|
572
565
|
|
573
566
|
def parse_single_replace_operation(data, keys)
|
574
|
-
if data[:id].nil?
|
575
|
-
raise JSONAPI::Exceptions::MissingKey.new
|
576
|
-
end
|
567
|
+
fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
|
577
568
|
|
578
569
|
type = data[:type]
|
579
570
|
if type.nil? || type != format_key(@resource_klass._type).to_s
|
580
|
-
|
571
|
+
fail JSONAPI::Exceptions::ParameterMissing.new(:type)
|
581
572
|
end
|
582
573
|
|
583
574
|
key = data[:id]
|
584
|
-
|
585
|
-
|
575
|
+
unless keys.include?(key)
|
576
|
+
fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
|
586
577
|
end
|
587
578
|
|
588
|
-
|
589
|
-
data.delete(:id)
|
590
|
-
end
|
579
|
+
data.delete(:id) unless keys.include?(:id)
|
591
580
|
|
592
581
|
verify_type(data[:type])
|
593
582
|
|
594
583
|
@operations.push JSONAPI::ReplaceFieldsOperation.new(
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
}
|
601
|
-
)
|
584
|
+
@resource_klass,
|
585
|
+
context: @context,
|
586
|
+
resource_id: key,
|
587
|
+
data: parse_params(data, updatable_fields)
|
588
|
+
)
|
602
589
|
end
|
603
590
|
|
604
591
|
def parse_replace_operation(data, keys)
|
605
592
|
if data.is_a?(Array)
|
606
|
-
if keys.count != data.count
|
607
|
-
raise JSONAPI::Exceptions::CountMismatch
|
608
|
-
end
|
593
|
+
fail JSONAPI::Exceptions::CountMismatch if keys.count != data.count
|
609
594
|
|
610
595
|
data.each do |object_params|
|
611
596
|
parse_single_replace_operation(object_params, keys)
|
@@ -623,12 +608,10 @@ module JSONAPI
|
|
623
608
|
|
624
609
|
keys.each do |key|
|
625
610
|
@operations.push JSONAPI::RemoveResourceOperation.new(
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
}
|
631
|
-
)
|
611
|
+
@resource_klass,
|
612
|
+
context: @context,
|
613
|
+
resource_id: key
|
614
|
+
)
|
632
615
|
end
|
633
616
|
rescue ActionController::UnpermittedParameters => e
|
634
617
|
@errors.concat(JSONAPI::Exceptions::ParametersNotAllowed.new(e.params).errors)
|
@@ -646,29 +629,25 @@ module JSONAPI
|
|
646
629
|
keys = parse_key_array(params[:keys])
|
647
630
|
keys.each do |key|
|
648
631
|
@operations.push JSONAPI::RemoveHasManyAssociationOperation.new(
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
}
|
656
|
-
)
|
632
|
+
resource_klass,
|
633
|
+
context: @context,
|
634
|
+
resource_id: parent_key,
|
635
|
+
association_type: association_type,
|
636
|
+
associated_key: key
|
637
|
+
)
|
657
638
|
end
|
658
639
|
else
|
659
640
|
@operations.push JSONAPI::RemoveHasOneAssociationOperation.new(
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
}
|
666
|
-
)
|
641
|
+
resource_klass,
|
642
|
+
context: @context,
|
643
|
+
resource_id: parent_key,
|
644
|
+
association_type: association_type
|
645
|
+
)
|
667
646
|
end
|
668
647
|
end
|
669
648
|
|
670
649
|
def parse_key_array(raw)
|
671
|
-
|
650
|
+
@resource_klass.verify_keys(raw.split(/,/), context)
|
672
651
|
end
|
673
652
|
|
674
653
|
def format_key(key)
|