jsonapi-resources 0.4.4 → 0.5.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/.travis.yml +1 -0
- data/README.md +73 -39
- data/lib/jsonapi-resources.rb +2 -1
- data/lib/jsonapi/acts_as_resource_controller.rb +5 -5
- data/lib/jsonapi/configuration.rb +13 -1
- data/lib/jsonapi/error.rb +2 -2
- data/lib/jsonapi/exceptions.rb +32 -20
- data/lib/jsonapi/link_builder.rb +141 -0
- data/lib/jsonapi/operation.rb +34 -34
- data/lib/jsonapi/operation_result.rb +3 -3
- data/lib/jsonapi/operations_processor.rb +7 -7
- data/lib/jsonapi/{association.rb → relationship.rb} +12 -4
- data/lib/jsonapi/request.rb +85 -85
- data/lib/jsonapi/resource.rb +153 -121
- data/lib/jsonapi/resource_serializer.rb +89 -92
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +42 -42
- data/test/controllers/controller_test.rb +90 -100
- data/test/fixtures/active_record.rb +38 -5
- data/test/fixtures/author_details.yml +9 -0
- data/test/fixtures/vehicles.yml +10 -2
- data/test/integration/requests/request_test.rb +17 -17
- data/test/integration/routes/routes_test.rb +20 -20
- data/test/test_helper.rb +18 -1
- data/test/unit/jsonapi_request/jsonapi_request_test.rb +1 -1
- data/test/unit/operation/operations_processor_test.rb +21 -21
- data/test/unit/resource/resource_test.rb +13 -13
- data/test/unit/serializer/link_builder_test.rb +183 -0
- data/test/unit/serializer/polymorphic_serializer_test.rb +73 -74
- data/test/unit/serializer/serializer_test.rb +342 -91
- metadata +8 -3
@@ -0,0 +1,141 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class LinkBuilder
|
3
|
+
attr_reader :base_url,
|
4
|
+
:primary_resource_klass,
|
5
|
+
:route_formatter
|
6
|
+
|
7
|
+
def initialize(config = {})
|
8
|
+
@base_url = config[:base_url]
|
9
|
+
@primary_resource_klass = config[:primary_resource_klass]
|
10
|
+
@route_formatter = config[:route_formatter]
|
11
|
+
@is_engine = !!engine_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def engine?
|
15
|
+
@is_engine
|
16
|
+
end
|
17
|
+
|
18
|
+
def engine_name
|
19
|
+
@engine_name ||= build_engine_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def primary_resources_url
|
23
|
+
if engine?
|
24
|
+
engine_primary_resources_url
|
25
|
+
else
|
26
|
+
regular_primary_resources_url
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def query_link(query_params)
|
31
|
+
"#{ primary_resources_url }?#{ query_params.to_query }"
|
32
|
+
end
|
33
|
+
|
34
|
+
def relationships_related_link(source, relationship)
|
35
|
+
"#{ self_link(source) }/#{ route_for_relationship(relationship) }"
|
36
|
+
end
|
37
|
+
|
38
|
+
def relationships_self_link(source, relationship)
|
39
|
+
"#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self_link(source)
|
43
|
+
if engine?
|
44
|
+
engine_resource_url(source)
|
45
|
+
else
|
46
|
+
regular_resource_url(source)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def build_engine_name
|
53
|
+
scopes = module_scopes_from_class(primary_resource_klass)
|
54
|
+
|
55
|
+
unless scopes.empty?
|
56
|
+
"#{ scopes.first.to_s.camelize }::Engine".safe_constantize
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def engine_path_from_resource_class(klass)
|
61
|
+
path_name = engine_resources_path_name_from_class(klass)
|
62
|
+
engine_name.routes.url_helpers.public_send(path_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def engine_primary_resources_path
|
66
|
+
engine_path_from_resource_class(primary_resource_klass)
|
67
|
+
end
|
68
|
+
|
69
|
+
def engine_primary_resources_url
|
70
|
+
"#{ base_url }#{ engine_primary_resources_path }"
|
71
|
+
end
|
72
|
+
|
73
|
+
def engine_resource_path(source)
|
74
|
+
resource_path_name = engine_resource_path_name_from_source(source)
|
75
|
+
engine_name.routes.url_helpers.public_send(resource_path_name, source.id)
|
76
|
+
end
|
77
|
+
|
78
|
+
def engine_resource_path_name_from_source(source)
|
79
|
+
scopes = module_scopes_from_class(source.class)[1..-1]
|
80
|
+
base_path_name = scopes.map { |scope| scope.downcase }.join("_")
|
81
|
+
end_path_name = source.class._type.to_s.singularize
|
82
|
+
"#{ base_path_name }_#{ end_path_name }_path"
|
83
|
+
end
|
84
|
+
|
85
|
+
def engine_resource_url(source)
|
86
|
+
"#{ base_url }#{ engine_resource_path(source) }"
|
87
|
+
end
|
88
|
+
|
89
|
+
def engine_resources_path_name_from_class(klass)
|
90
|
+
scopes = module_scopes_from_class(klass)[1..-1]
|
91
|
+
base_path_name = scopes.map { |scope| scope.downcase }.join("_")
|
92
|
+
end_path_name = klass._type.to_s
|
93
|
+
"#{ base_path_name }_#{ end_path_name }_path"
|
94
|
+
end
|
95
|
+
|
96
|
+
def format_route(route)
|
97
|
+
route_formatter.format(route.to_s)
|
98
|
+
end
|
99
|
+
|
100
|
+
def formatted_module_path_from_class(klass)
|
101
|
+
scopes = module_scopes_from_class(klass)
|
102
|
+
|
103
|
+
unless scopes.empty?
|
104
|
+
"/#{ scopes.map(&:downcase).join('/') }/"
|
105
|
+
else
|
106
|
+
"/"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def module_scopes_from_class(klass)
|
111
|
+
klass.name.to_s.split("::")[0...-1]
|
112
|
+
end
|
113
|
+
|
114
|
+
def regular_primary_resources_path
|
115
|
+
[
|
116
|
+
formatted_module_path_from_class(primary_resource_klass),
|
117
|
+
route_formatter.format(primary_resource_klass._type.to_s),
|
118
|
+
].join
|
119
|
+
end
|
120
|
+
|
121
|
+
def regular_primary_resources_url
|
122
|
+
"#{ base_url }#{ regular_primary_resources_path }"
|
123
|
+
end
|
124
|
+
|
125
|
+
def regular_resource_path(source)
|
126
|
+
[
|
127
|
+
formatted_module_path_from_class(source.class),
|
128
|
+
route_formatter.format(source.class._type.to_s),
|
129
|
+
"/#{ source.id }",
|
130
|
+
].join
|
131
|
+
end
|
132
|
+
|
133
|
+
def regular_resource_url(source)
|
134
|
+
"#{ base_url }#{ regular_resource_path(source) }"
|
135
|
+
end
|
136
|
+
|
137
|
+
def route_for_relationship(relationship)
|
138
|
+
format_route(relationship.name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/jsonapi/operation.rb
CHANGED
@@ -90,12 +90,12 @@ module JSONAPI
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
class
|
94
|
-
attr_reader :parent_key, :
|
93
|
+
class ShowRelationshipOperation < Operation
|
94
|
+
attr_reader :parent_key, :relationship_type
|
95
95
|
|
96
96
|
def initialize(resource_klass, options = {})
|
97
97
|
@parent_key = options.fetch(:parent_key)
|
98
|
-
@
|
98
|
+
@relationship_type = options.fetch(:relationship_type)
|
99
99
|
@transactional = false
|
100
100
|
super(resource_klass, options)
|
101
101
|
end
|
@@ -105,7 +105,7 @@ module JSONAPI
|
|
105
105
|
|
106
106
|
return JSONAPI::LinksObjectOperationResult.new(:ok,
|
107
107
|
parent_resource,
|
108
|
-
resource_klass.
|
108
|
+
resource_klass._relationship(@relationship_type))
|
109
109
|
|
110
110
|
rescue JSONAPI::Exceptions::Error => e
|
111
111
|
return JSONAPI::ErrorsOperationResult.new(e.errors[0].code, e.errors)
|
@@ -113,12 +113,12 @@ module JSONAPI
|
|
113
113
|
end
|
114
114
|
|
115
115
|
class ShowRelatedResourceOperation < Operation
|
116
|
-
attr_reader :source_klass, :source_id, :
|
116
|
+
attr_reader :source_klass, :source_id, :relationship_type
|
117
117
|
|
118
118
|
def initialize(resource_klass, options = {})
|
119
119
|
@source_klass = options.fetch(:source_klass)
|
120
120
|
@source_id = options.fetch(:source_id)
|
121
|
-
@
|
121
|
+
@relationship_type = options.fetch(:relationship_type)
|
122
122
|
@transactional = false
|
123
123
|
super(resource_klass, options)
|
124
124
|
end
|
@@ -126,7 +126,7 @@ module JSONAPI
|
|
126
126
|
def apply
|
127
127
|
source_resource = @source_klass.find_by_key(@source_id, context: @context)
|
128
128
|
|
129
|
-
related_resource = source_resource.
|
129
|
+
related_resource = source_resource.public_send(@relationship_type)
|
130
130
|
|
131
131
|
return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
|
132
132
|
|
@@ -136,12 +136,12 @@ module JSONAPI
|
|
136
136
|
end
|
137
137
|
|
138
138
|
class ShowRelatedResourcesOperation < Operation
|
139
|
-
attr_reader :source_klass, :source_id, :
|
139
|
+
attr_reader :source_klass, :source_id, :relationship_type, :filters, :sort_criteria, :paginator
|
140
140
|
|
141
141
|
def initialize(resource_klass, options = {})
|
142
142
|
@source_klass = options.fetch(:source_klass)
|
143
143
|
@source_id = options.fetch(:source_id)
|
144
|
-
@
|
144
|
+
@relationship_type = options.fetch(:relationship_type)
|
145
145
|
@filters = options[:filters]
|
146
146
|
@sort_criteria = options[:sort_criteria]
|
147
147
|
@paginator = options[:paginator]
|
@@ -152,7 +152,7 @@ module JSONAPI
|
|
152
152
|
def apply
|
153
153
|
source_resource = @source_klass.find_by_key(@source_id, context: @context)
|
154
154
|
|
155
|
-
related_resource = source_resource.
|
155
|
+
related_resource = source_resource.public_send(@relationship_type,
|
156
156
|
filters: @filters,
|
157
157
|
sort_criteria: @sort_criteria,
|
158
158
|
paginator: @paginator)
|
@@ -218,109 +218,109 @@ module JSONAPI
|
|
218
218
|
end
|
219
219
|
end
|
220
220
|
|
221
|
-
class
|
222
|
-
attr_reader :resource_id, :
|
221
|
+
class ReplaceToOneRelationshipOperation < Operation
|
222
|
+
attr_reader :resource_id, :relationship_type, :key_value
|
223
223
|
|
224
224
|
def initialize(resource_klass, options = {})
|
225
225
|
@resource_id = options.fetch(:resource_id)
|
226
226
|
@key_value = options.fetch(:key_value)
|
227
|
-
@
|
227
|
+
@relationship_type = options.fetch(:relationship_type).to_sym
|
228
228
|
super(resource_klass, options)
|
229
229
|
end
|
230
230
|
|
231
231
|
def apply
|
232
232
|
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
233
|
-
result = resource.
|
233
|
+
result = resource.replace_to_one_link(@relationship_type, @key_value)
|
234
234
|
|
235
235
|
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
236
236
|
end
|
237
237
|
end
|
238
238
|
|
239
|
-
class
|
240
|
-
attr_reader :resource_id, :
|
239
|
+
class ReplacePolymorphicToOneRelationshipOperation < Operation
|
240
|
+
attr_reader :resource_id, :relationship_type, :key_value, :key_type
|
241
241
|
|
242
242
|
def initialize(resource_klass, options = {})
|
243
243
|
@resource_id = options.fetch(:resource_id)
|
244
244
|
@key_value = options.fetch(:key_value)
|
245
245
|
@key_type = options.fetch(:key_type)
|
246
|
-
@
|
246
|
+
@relationship_type = options.fetch(:relationship_type).to_sym
|
247
247
|
super(resource_klass, options)
|
248
248
|
end
|
249
249
|
|
250
250
|
def apply
|
251
251
|
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
252
|
-
result = resource.
|
252
|
+
result = resource.replace_polymorphic_to_one_link(@relationship_type, @key_value, @key_type)
|
253
253
|
|
254
254
|
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
255
255
|
end
|
256
256
|
end
|
257
257
|
|
258
|
-
class
|
259
|
-
attr_reader :resource_id, :
|
258
|
+
class CreateToManyRelationshipOperation < Operation
|
259
|
+
attr_reader :resource_id, :relationship_type, :data
|
260
260
|
|
261
261
|
def initialize(resource_klass, options)
|
262
262
|
@resource_id = options.fetch(:resource_id)
|
263
263
|
@data = options.fetch(:data)
|
264
|
-
@
|
264
|
+
@relationship_type = options.fetch(:relationship_type).to_sym
|
265
265
|
super(resource_klass, options)
|
266
266
|
end
|
267
267
|
|
268
268
|
def apply
|
269
269
|
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
270
|
-
result = resource.
|
270
|
+
result = resource.create_to_many_links(@relationship_type, @data)
|
271
271
|
|
272
272
|
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
273
273
|
end
|
274
274
|
end
|
275
275
|
|
276
|
-
class
|
277
|
-
attr_reader :resource_id, :
|
276
|
+
class ReplaceToManyRelationshipOperation < Operation
|
277
|
+
attr_reader :resource_id, :relationship_type, :data
|
278
278
|
|
279
279
|
def initialize(resource_klass, options)
|
280
280
|
@resource_id = options.fetch(:resource_id)
|
281
281
|
@data = options.fetch(:data)
|
282
|
-
@
|
282
|
+
@relationship_type = options.fetch(:relationship_type).to_sym
|
283
283
|
super(resource_klass, options)
|
284
284
|
end
|
285
285
|
|
286
286
|
def apply
|
287
287
|
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
288
|
-
result = resource.
|
288
|
+
result = resource.replace_to_many_links(@relationship_type, @data)
|
289
289
|
|
290
290
|
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
291
291
|
end
|
292
292
|
end
|
293
293
|
|
294
|
-
class
|
295
|
-
attr_reader :resource_id, :
|
294
|
+
class RemoveToManyRelationshipOperation < Operation
|
295
|
+
attr_reader :resource_id, :relationship_type, :associated_key
|
296
296
|
|
297
297
|
def initialize(resource_klass, options)
|
298
298
|
@resource_id = options.fetch(:resource_id)
|
299
299
|
@associated_key = options.fetch(:associated_key)
|
300
|
-
@
|
300
|
+
@relationship_type = options.fetch(:relationship_type).to_sym
|
301
301
|
super(resource_klass, options)
|
302
302
|
end
|
303
303
|
|
304
304
|
def apply
|
305
305
|
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
306
|
-
result = resource.
|
306
|
+
result = resource.remove_to_many_link(@relationship_type, @associated_key)
|
307
307
|
|
308
308
|
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
309
309
|
end
|
310
310
|
end
|
311
311
|
|
312
|
-
class
|
313
|
-
attr_reader :resource_id, :
|
312
|
+
class RemoveToOneRelationshipOperation < Operation
|
313
|
+
attr_reader :resource_id, :relationship_type
|
314
314
|
|
315
315
|
def initialize(resource_klass, options)
|
316
316
|
@resource_id = options.fetch(:resource_id)
|
317
|
-
@
|
317
|
+
@relationship_type = options.fetch(:relationship_type).to_sym
|
318
318
|
super(resource_klass, options)
|
319
319
|
end
|
320
320
|
|
321
321
|
def apply
|
322
322
|
resource = @resource_klass.find_by_key(@resource_id, context: @context)
|
323
|
-
result = resource.
|
323
|
+
result = resource.remove_to_one_link(@relationship_type)
|
324
324
|
|
325
325
|
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
326
326
|
end
|
@@ -43,11 +43,11 @@ module JSONAPI
|
|
43
43
|
end
|
44
44
|
|
45
45
|
class LinksObjectOperationResult < OperationResult
|
46
|
-
attr_accessor :parent_resource, :
|
46
|
+
attr_accessor :parent_resource, :relationship
|
47
47
|
|
48
|
-
def initialize(code, parent_resource,
|
48
|
+
def initialize(code, parent_resource, relationship, options = {})
|
49
49
|
@parent_resource = parent_resource
|
50
|
-
@
|
50
|
+
@relationship = relationship
|
51
51
|
super(code, options)
|
52
52
|
end
|
53
53
|
end
|
@@ -5,18 +5,18 @@ module JSONAPI
|
|
5
5
|
:operations,
|
6
6
|
:find_operation,
|
7
7
|
:show_operation,
|
8
|
-
:
|
8
|
+
:show_relationship_operation,
|
9
9
|
:show_related_resource_operation,
|
10
10
|
:show_related_resources_operation,
|
11
11
|
:create_resource_operation,
|
12
12
|
:remove_resource_operation,
|
13
13
|
:replace_fields_operation,
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
14
|
+
:replace_to_one_relationship_operation,
|
15
|
+
:replace_polymorphic_to_one_relationship_operation,
|
16
|
+
:create_to_many_relationship_operation,
|
17
|
+
:replace_to_many_relationship_operation,
|
18
|
+
:remove_to_many_relationship_operation,
|
19
|
+
:remove_to_one_relationship_operation
|
20
20
|
|
21
21
|
class << self
|
22
22
|
def operations_processor_for(operations_processor)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module JSONAPI
|
2
|
-
class
|
2
|
+
class Relationship
|
3
3
|
attr_reader :acts_as_set, :foreign_key, :type, :options, :name,
|
4
|
-
:class_name, :polymorphic
|
4
|
+
:class_name, :polymorphic, :always_include_linkage_data
|
5
5
|
|
6
6
|
def initialize(name, options = {})
|
7
7
|
@name = name.to_s
|
@@ -11,6 +11,7 @@ module JSONAPI
|
|
11
11
|
@module_path = options[:module_path] || ''
|
12
12
|
@relation_name = options.fetch(:relation_name, @name)
|
13
13
|
@polymorphic = options.fetch(:polymorphic, false) == true
|
14
|
+
@always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
|
14
15
|
end
|
15
16
|
|
16
17
|
alias_method :polymorphic?, :polymorphic
|
@@ -45,12 +46,19 @@ module JSONAPI
|
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
class
|
49
|
+
class ToOne < Relationship
|
50
|
+
attr_reader :foreign_key_on
|
51
|
+
|
49
52
|
def initialize(name, options = {})
|
50
53
|
super
|
51
54
|
@class_name = options.fetch(:class_name, name.to_s.camelize)
|
52
55
|
@type = class_name.underscore.pluralize.to_sym
|
53
56
|
@foreign_key ||= "#{name}_id".to_sym
|
57
|
+
@foreign_key_on = options.fetch(:foreign_key_on, :self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def belongs_to?
|
61
|
+
foreign_key_on == :self
|
54
62
|
end
|
55
63
|
|
56
64
|
def polymorphic_type
|
@@ -58,7 +66,7 @@ module JSONAPI
|
|
58
66
|
end
|
59
67
|
end
|
60
68
|
|
61
|
-
class
|
69
|
+
class ToMany < Relationship
|
62
70
|
def initialize(name, options = {})
|
63
71
|
super
|
64
72
|
@class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
|
data/lib/jsonapi/request.rb
CHANGED
@@ -58,7 +58,7 @@ module JSONAPI
|
|
58
58
|
parse_filters(params[:filter])
|
59
59
|
parse_sort_criteria(params[:sort])
|
60
60
|
parse_pagination(params[:page])
|
61
|
-
add_show_related_resource_operation(params[:
|
61
|
+
add_show_related_resource_operation(params[:relationship])
|
62
62
|
end
|
63
63
|
|
64
64
|
def setup_get_related_resources_action(params)
|
@@ -69,7 +69,7 @@ module JSONAPI
|
|
69
69
|
parse_filters(params[:filter])
|
70
70
|
parse_sort_criteria(params[:sort])
|
71
71
|
parse_pagination(params[:page])
|
72
|
-
add_show_related_resources_operation(params[:
|
72
|
+
add_show_related_resources_operation(params[:relationship])
|
73
73
|
end
|
74
74
|
|
75
75
|
def setup_show_action(params)
|
@@ -79,8 +79,8 @@ module JSONAPI
|
|
79
79
|
add_show_operation
|
80
80
|
end
|
81
81
|
|
82
|
-
def
|
83
|
-
|
82
|
+
def setup_show_relationship_action(params)
|
83
|
+
add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
|
84
84
|
end
|
85
85
|
|
86
86
|
def setup_create_action(params)
|
@@ -89,15 +89,15 @@ module JSONAPI
|
|
89
89
|
parse_add_operation(params.require(:data))
|
90
90
|
end
|
91
91
|
|
92
|
-
def
|
93
|
-
|
94
|
-
params.require(:
|
92
|
+
def setup_create_relationship_action(params)
|
93
|
+
parse_add_relationship_operation(params.require(:data),
|
94
|
+
params.require(:relationship),
|
95
95
|
params.require(@resource_klass._as_parent_key))
|
96
96
|
end
|
97
97
|
|
98
|
-
def
|
99
|
-
|
100
|
-
params.require(:
|
98
|
+
def setup_update_relationship_action(params)
|
99
|
+
parse_update_relationship_operation(params.fetch(:data),
|
100
|
+
params.require(:relationship),
|
101
101
|
params.require(@resource_klass._as_parent_key))
|
102
102
|
end
|
103
103
|
|
@@ -111,8 +111,8 @@ module JSONAPI
|
|
111
111
|
parse_remove_operation(params)
|
112
112
|
end
|
113
113
|
|
114
|
-
def
|
115
|
-
|
114
|
+
def setup_destroy_relationship_action(params)
|
115
|
+
parse_remove_relationship_operation(params)
|
116
116
|
end
|
117
117
|
|
118
118
|
def initialize_source(params)
|
@@ -158,7 +158,7 @@ module JSONAPI
|
|
158
158
|
end
|
159
159
|
|
160
160
|
if type_resource.nil? || !(@resource_klass._type == underscored_type ||
|
161
|
-
@resource_klass.
|
161
|
+
@resource_klass._has_relationship?(underscored_type))
|
162
162
|
@errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
|
163
163
|
else
|
164
164
|
unless values.nil?
|
@@ -180,12 +180,12 @@ module JSONAPI
|
|
180
180
|
end
|
181
181
|
|
182
182
|
def check_include(resource_klass, include_parts)
|
183
|
-
|
183
|
+
relationship_name = unformat_key(include_parts.first)
|
184
184
|
|
185
|
-
|
186
|
-
if
|
185
|
+
relationship = resource_klass._relationship(relationship_name)
|
186
|
+
if relationship && format_key(relationship_name) == include_parts.first
|
187
187
|
unless include_parts.last.empty?
|
188
|
-
check_include(Resource.resource_for(@resource_klass.module_path +
|
188
|
+
check_include(Resource.resource_for(@resource_klass.module_path + relationship.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),
|
@@ -280,30 +280,30 @@ module JSONAPI
|
|
280
280
|
)
|
281
281
|
end
|
282
282
|
|
283
|
-
def
|
284
|
-
@operations.push JSONAPI::
|
283
|
+
def add_show_relationship_operation(relationship_type, parent_key)
|
284
|
+
@operations.push JSONAPI::ShowRelationshipOperation.new(
|
285
285
|
@resource_klass,
|
286
286
|
context: @context,
|
287
|
-
|
287
|
+
relationship_type: relationship_type,
|
288
288
|
parent_key: @resource_klass.verify_key(parent_key)
|
289
289
|
)
|
290
290
|
end
|
291
291
|
|
292
|
-
def add_show_related_resource_operation(
|
292
|
+
def add_show_related_resource_operation(relationship_type)
|
293
293
|
@operations.push JSONAPI::ShowRelatedResourceOperation.new(
|
294
294
|
@resource_klass,
|
295
295
|
context: @context,
|
296
|
-
|
296
|
+
relationship_type: relationship_type,
|
297
297
|
source_klass: @source_klass,
|
298
298
|
source_id: @source_id
|
299
299
|
)
|
300
300
|
end
|
301
301
|
|
302
|
-
def add_show_related_resources_operation(
|
302
|
+
def add_show_related_resources_operation(relationship_type)
|
303
303
|
@operations.push JSONAPI::ShowRelatedResourcesOperation.new(
|
304
304
|
@resource_klass,
|
305
305
|
context: @context,
|
306
|
-
|
306
|
+
relationship_type: relationship_type,
|
307
307
|
source_klass: @source_klass,
|
308
308
|
source_id: @source_id,
|
309
309
|
filters: @source_klass.verify_filters(@filters, @context),
|
@@ -346,7 +346,7 @@ module JSONAPI
|
|
346
346
|
end
|
347
347
|
end
|
348
348
|
|
349
|
-
def
|
349
|
+
def parse_to_one_links_object(raw)
|
350
350
|
if raw.nil?
|
351
351
|
return {
|
352
352
|
type: nil,
|
@@ -364,13 +364,13 @@ module JSONAPI
|
|
364
364
|
}
|
365
365
|
end
|
366
366
|
|
367
|
-
def
|
367
|
+
def parse_to_many_links_object(raw)
|
368
368
|
fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
|
369
369
|
|
370
370
|
links_object = {}
|
371
371
|
if raw.is_a?(Array)
|
372
372
|
raw.each do |link|
|
373
|
-
link_object =
|
373
|
+
link_object = parse_to_one_links_object(link)
|
374
374
|
links_object[link_object[:type]] ||= []
|
375
375
|
links_object[link_object[:type]].push(link_object[:id])
|
376
376
|
end
|
@@ -384,39 +384,39 @@ module JSONAPI
|
|
384
384
|
verify_permitted_params(params, allowed_fields)
|
385
385
|
|
386
386
|
checked_attributes = {}
|
387
|
-
|
388
|
-
|
387
|
+
checked_to_one_relationships = {}
|
388
|
+
checked_to_many_relationships = {}
|
389
389
|
|
390
390
|
params.each do |key, value|
|
391
391
|
case key.to_s
|
392
392
|
when 'relationships'
|
393
393
|
value.each do |link_key, link_value|
|
394
394
|
param = unformat_key(link_key)
|
395
|
-
|
396
|
-
if
|
395
|
+
relationship = @resource_klass._relationship(param)
|
396
|
+
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
397
397
|
if link_value.nil?
|
398
398
|
linkage = nil
|
399
399
|
else
|
400
400
|
linkage = link_value[:data]
|
401
401
|
end
|
402
402
|
|
403
|
-
links_object =
|
404
|
-
if !
|
403
|
+
links_object = parse_to_one_links_object(linkage)
|
404
|
+
if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s)
|
405
405
|
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
406
406
|
end
|
407
407
|
|
408
408
|
unless links_object[:id].nil?
|
409
|
-
|
410
|
-
|
411
|
-
if
|
412
|
-
|
409
|
+
relationship_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
|
410
|
+
relationship_id = relationship_resource.verify_key(links_object[:id], @context)
|
411
|
+
if relationship.polymorphic?
|
412
|
+
checked_to_one_relationships[param] = { id: relationship_id, type: unformat_key(links_object[:type].to_s) }
|
413
413
|
else
|
414
|
-
|
414
|
+
checked_to_one_relationships[param] = relationship_id
|
415
415
|
end
|
416
416
|
else
|
417
|
-
|
417
|
+
checked_to_one_relationships[param] = nil
|
418
418
|
end
|
419
|
-
elsif
|
419
|
+
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
420
420
|
if link_value.is_a?(Array) && link_value.length == 0
|
421
421
|
linkage = []
|
422
422
|
elsif link_value.is_a?(Hash)
|
@@ -425,22 +425,22 @@ module JSONAPI
|
|
425
425
|
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
426
426
|
end
|
427
427
|
|
428
|
-
links_object =
|
428
|
+
links_object = parse_to_many_links_object(linkage)
|
429
429
|
|
430
|
-
# Since we do not yet support polymorphic
|
431
|
-
#
|
432
|
-
# ToDo: Support Polymorphic
|
430
|
+
# Since we do not yet support polymorphic relationships we will raise an error if the type does not match the
|
431
|
+
# relationship's type.
|
432
|
+
# ToDo: Support Polymorphic relationships
|
433
433
|
|
434
434
|
if links_object.length == 0
|
435
|
-
|
435
|
+
checked_to_many_relationships[param] = []
|
436
436
|
else
|
437
|
-
if links_object.length > 1 || !links_object.has_key?(unformat_key(
|
437
|
+
if links_object.length > 1 || !links_object.has_key?(unformat_key(relationship.type).to_s)
|
438
438
|
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
439
439
|
end
|
440
440
|
|
441
441
|
links_object.each_pair do |type, keys|
|
442
|
-
|
443
|
-
|
442
|
+
relationship_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
|
443
|
+
checked_to_many_relationships[param] = relationship_resource.verify_keys(keys, @context)
|
444
444
|
end
|
445
445
|
end
|
446
446
|
end
|
@@ -457,8 +457,8 @@ module JSONAPI
|
|
457
457
|
|
458
458
|
return {
|
459
459
|
'attributes' => checked_attributes,
|
460
|
-
'
|
461
|
-
'
|
460
|
+
'to_one' => checked_to_one_relationships,
|
461
|
+
'to_many' => checked_to_many_relationships
|
462
462
|
}.deep_transform_keys { |key| unformat_key(key) }
|
463
463
|
end
|
464
464
|
|
@@ -501,64 +501,64 @@ module JSONAPI
|
|
501
501
|
end
|
502
502
|
# :nocov:
|
503
503
|
|
504
|
-
def
|
505
|
-
|
504
|
+
def parse_add_relationship_operation(data, relationship_type, parent_key)
|
505
|
+
relationship = resource_klass._relationship(relationship_type)
|
506
506
|
|
507
|
-
if
|
508
|
-
object_params = { relationships: { format_key(
|
507
|
+
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
508
|
+
object_params = { relationships: { format_key(relationship.name) => { data: data } } }
|
509
509
|
verified_param_set = parse_params(object_params, updatable_fields)
|
510
510
|
|
511
|
-
@operations.push JSONAPI::
|
511
|
+
@operations.push JSONAPI::CreateToManyRelationshipOperation.new(
|
512
512
|
resource_klass,
|
513
513
|
context: @context,
|
514
514
|
resource_id: parent_key,
|
515
|
-
|
516
|
-
data: verified_param_set[:
|
515
|
+
relationship_type: relationship_type,
|
516
|
+
data: verified_param_set[:to_many].values[0]
|
517
517
|
)
|
518
518
|
end
|
519
519
|
end
|
520
520
|
|
521
|
-
def
|
522
|
-
|
523
|
-
if
|
524
|
-
if
|
525
|
-
object_params = { relationships: { format_key(
|
521
|
+
def parse_update_relationship_operation(data, relationship_type, parent_key)
|
522
|
+
relationship = resource_klass._relationship(relationship_type)
|
523
|
+
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
524
|
+
if relationship.polymorphic?
|
525
|
+
object_params = { relationships: { format_key(relationship.name) => { data: data } } }
|
526
526
|
verified_param_set = parse_params(object_params, updatable_fields)
|
527
527
|
|
528
|
-
@operations.push JSONAPI::
|
528
|
+
@operations.push JSONAPI::ReplacePolymorphicToOneRelationshipOperation.new(
|
529
529
|
resource_klass,
|
530
530
|
context: @context,
|
531
531
|
resource_id: parent_key,
|
532
|
-
|
533
|
-
key_value: verified_param_set[:
|
534
|
-
key_type: verified_param_set[:
|
532
|
+
relationship_type: relationship_type,
|
533
|
+
key_value: verified_param_set[:to_one].values[0][:id],
|
534
|
+
key_type: verified_param_set[:to_one].values[0][:type]
|
535
535
|
)
|
536
536
|
else
|
537
|
-
object_params = { relationships: { format_key(
|
537
|
+
object_params = { relationships: { format_key(relationship.name) => { data: data } } }
|
538
538
|
verified_param_set = parse_params(object_params, updatable_fields)
|
539
539
|
|
540
|
-
@operations.push JSONAPI::
|
540
|
+
@operations.push JSONAPI::ReplaceToOneRelationshipOperation.new(
|
541
541
|
resource_klass,
|
542
542
|
context: @context,
|
543
543
|
resource_id: parent_key,
|
544
|
-
|
545
|
-
key_value: verified_param_set[:
|
544
|
+
relationship_type: relationship_type,
|
545
|
+
key_value: verified_param_set[:to_one].values[0]
|
546
546
|
)
|
547
547
|
end
|
548
|
-
elsif
|
549
|
-
unless
|
550
|
-
fail JSONAPI::Exceptions::
|
548
|
+
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
549
|
+
unless relationship.acts_as_set
|
550
|
+
fail JSONAPI::Exceptions::ToManySetReplacementForbidden.new
|
551
551
|
end
|
552
552
|
|
553
|
-
object_params = { relationships: { format_key(
|
553
|
+
object_params = { relationships: { format_key(relationship.name) => { data: data } } }
|
554
554
|
verified_param_set = parse_params(object_params, updatable_fields)
|
555
555
|
|
556
|
-
@operations.push JSONAPI::
|
556
|
+
@operations.push JSONAPI::ReplaceToManyRelationshipOperation.new(
|
557
557
|
resource_klass,
|
558
558
|
context: @context,
|
559
559
|
resource_id: parent_key,
|
560
|
-
|
561
|
-
data: verified_param_set[:
|
560
|
+
relationship_type: relationship_type,
|
561
|
+
data: verified_param_set[:to_many].values[0]
|
562
562
|
)
|
563
563
|
end
|
564
564
|
end
|
@@ -619,29 +619,29 @@ module JSONAPI
|
|
619
619
|
@errors.concat(e.errors)
|
620
620
|
end
|
621
621
|
|
622
|
-
def
|
623
|
-
|
622
|
+
def parse_remove_relationship_operation(params)
|
623
|
+
relationship_type = params[:relationship]
|
624
624
|
|
625
625
|
parent_key = params[resource_klass._as_parent_key]
|
626
626
|
|
627
|
-
|
628
|
-
if
|
627
|
+
relationship = resource_klass._relationship(relationship_type)
|
628
|
+
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
629
629
|
keys = parse_key_array(params[:keys])
|
630
630
|
keys.each do |key|
|
631
|
-
@operations.push JSONAPI::
|
631
|
+
@operations.push JSONAPI::RemoveToManyRelationshipOperation.new(
|
632
632
|
resource_klass,
|
633
633
|
context: @context,
|
634
634
|
resource_id: parent_key,
|
635
|
-
|
635
|
+
relationship_type: relationship_type,
|
636
636
|
associated_key: key
|
637
637
|
)
|
638
638
|
end
|
639
639
|
else
|
640
|
-
@operations.push JSONAPI::
|
640
|
+
@operations.push JSONAPI::RemoveToOneRelationshipOperation.new(
|
641
641
|
resource_klass,
|
642
642
|
context: @context,
|
643
643
|
resource_id: parent_key,
|
644
|
-
|
644
|
+
relationship_type: relationship_type
|
645
645
|
)
|
646
646
|
end
|
647
647
|
end
|