jsonapi-resources 0.4.2 → 0.4.3
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/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)
|