jsonapi-resources 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/README.md +186 -45
- data/lib/jsonapi/association.rb +6 -20
- data/lib/jsonapi/configuration.rb +27 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +41 -0
- data/lib/jsonapi/formatter.rb +94 -0
- data/lib/jsonapi/operation.rb +46 -9
- data/lib/jsonapi/request.rb +178 -48
- data/lib/jsonapi/resource.rb +51 -84
- data/lib/jsonapi/resource_controller.rb +53 -25
- data/lib/jsonapi/resource_for.rb +4 -6
- data/lib/jsonapi/resource_serializer.rb +59 -31
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +9 -1
- data/lib/jsonapi-resources.rb +2 -0
- data/test/controllers/controller_test.rb +706 -335
- data/test/fixtures/active_record.rb +60 -24
- data/test/integration/requests/request_test.rb +10 -15
- data/test/integration/routes/routes_test.rb +68 -20
- data/test/test_helper.rb +45 -3
- data/test/unit/operation/operations_processor_test.rb +49 -7
- data/test/unit/resource/resource_test.rb +2 -2
- data/test/unit/serializer/serializer_test.rb +483 -360
- metadata +5 -2
data/lib/jsonapi/resource.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'jsonapi/configuration'
|
1
2
|
require 'jsonapi/resource_for'
|
2
3
|
require 'jsonapi/association'
|
3
4
|
require 'action_dispatch/routing/mapper'
|
@@ -24,9 +25,15 @@ module JSONAPI
|
|
24
25
|
|
25
26
|
def create_has_many_link(association_type, association_key_value, context)
|
26
27
|
association = self.class._associations[association_type]
|
27
|
-
related_resource = self.class.resource_for(association.
|
28
|
+
related_resource = self.class.resource_for(association.type).find_by_key(association_key_value, context)
|
28
29
|
|
29
|
-
|
30
|
+
# ToDo: Add option to skip relations that already exist instead of returning an error?
|
31
|
+
relation = @object.send(association.type).where(association.primary_key => association_key_value).first
|
32
|
+
if relation.nil?
|
33
|
+
@object.send(association.type) << related_resource.object
|
34
|
+
else
|
35
|
+
raise JSONAPI::Exceptions::HasManyRelationExists.new(association_key_value)
|
36
|
+
end
|
30
37
|
end
|
31
38
|
|
32
39
|
def replace_has_many_links(association_type, association_key_values, context)
|
@@ -35,6 +42,18 @@ module JSONAPI
|
|
35
42
|
@object.send("#{association.key}=", association_key_values)
|
36
43
|
end
|
37
44
|
|
45
|
+
def create_has_one_link(association_type, association_key_value, context)
|
46
|
+
association = self.class._associations[association_type]
|
47
|
+
|
48
|
+
# ToDo: Add option to skip relations that already exist instead of returning an error?
|
49
|
+
relation = @object.send("#{association.key}")
|
50
|
+
if relation.nil?
|
51
|
+
@object.send("#{association.key}=", association_key_value)
|
52
|
+
else
|
53
|
+
raise JSONAPI::Exceptions::HasOneRelationExists.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
38
57
|
def replace_has_one_link(association_type, association_key_value, context)
|
39
58
|
association = self.class._associations[association_type]
|
40
59
|
|
@@ -44,7 +63,7 @@ module JSONAPI
|
|
44
63
|
def remove_has_many_link(association_type, key, context)
|
45
64
|
association = self.class._associations[association_type]
|
46
65
|
|
47
|
-
@object.send(association.
|
66
|
+
@object.send(association.type).delete(key)
|
48
67
|
end
|
49
68
|
|
50
69
|
def remove_has_one_link(association_type, context)
|
@@ -95,7 +114,7 @@ module JSONAPI
|
|
95
114
|
|
96
115
|
class << self
|
97
116
|
def inherited(base)
|
98
|
-
base._attributes = (_attributes ||
|
117
|
+
base._attributes = (_attributes || {}).dup
|
99
118
|
base._associations = (_associations || {}).dup
|
100
119
|
base._allowed_filters = (_allowed_filters || Set.new).dup
|
101
120
|
|
@@ -119,14 +138,13 @@ module JSONAPI
|
|
119
138
|
|
120
139
|
# Methods used in defining a resource class
|
121
140
|
def attributes(*attrs)
|
122
|
-
@_attributes.merge attrs
|
123
141
|
attrs.each do |attr|
|
124
142
|
attribute(attr)
|
125
143
|
end
|
126
144
|
end
|
127
145
|
|
128
|
-
def attribute(attr)
|
129
|
-
@_attributes
|
146
|
+
def attribute(attr, options = {})
|
147
|
+
@_attributes[attr] = options
|
130
148
|
define_method attr do
|
131
149
|
@object.send(attr)
|
132
150
|
end unless method_defined?(attr)
|
@@ -136,6 +154,10 @@ module JSONAPI
|
|
136
154
|
end unless method_defined?("#{attr}=")
|
137
155
|
end
|
138
156
|
|
157
|
+
def default_attribute_options
|
158
|
+
{format: :default}
|
159
|
+
end
|
160
|
+
|
139
161
|
def has_one(*attrs)
|
140
162
|
_associate(Association::HasOne, *attrs)
|
141
163
|
end
|
@@ -161,13 +183,17 @@ module JSONAPI
|
|
161
183
|
end
|
162
184
|
|
163
185
|
# Override in your resource to filter the updateable keys
|
164
|
-
def
|
165
|
-
|
186
|
+
def updateable_fields(context)
|
187
|
+
fields
|
166
188
|
end
|
167
189
|
|
168
190
|
# Override in your resource to filter the createable keys
|
169
|
-
def
|
170
|
-
|
191
|
+
def createable_fields(context)
|
192
|
+
fields
|
193
|
+
end
|
194
|
+
|
195
|
+
def fields
|
196
|
+
_updateable_associations | _attributes.keys
|
171
197
|
end
|
172
198
|
|
173
199
|
# Override this method if you have more complex requirements than this basic find method provides
|
@@ -178,7 +204,7 @@ module JSONAPI
|
|
178
204
|
filters.each do |filter, value|
|
179
205
|
if _associations.include?(filter)
|
180
206
|
if _associations[filter].is_a?(JSONAPI::Association::HasMany)
|
181
|
-
includes.push(filter
|
207
|
+
includes.push(filter)
|
182
208
|
where_filters["#{filter}.#{_associations[filter].primary_key}"] = value
|
183
209
|
else
|
184
210
|
where_filters["#{_associations[filter].key}"] = value
|
@@ -204,53 +230,6 @@ module JSONAPI
|
|
204
230
|
self.new(obj)
|
205
231
|
end
|
206
232
|
|
207
|
-
def verify_create_params(object_params, context = nil)
|
208
|
-
verify_params(object_params, createable(_updateable_associations | _attributes.to_a), context)
|
209
|
-
end
|
210
|
-
|
211
|
-
def verify_update_params(object_params, context = nil)
|
212
|
-
verify_params(object_params, updateable(_updateable_associations | _attributes.to_a), context)
|
213
|
-
end
|
214
|
-
|
215
|
-
def verify_params(object_params, allowed_params, context)
|
216
|
-
# push links into top level param list with attributes in order to check for invalid params
|
217
|
-
if object_params[:links]
|
218
|
-
object_params[:links].each do |link, value|
|
219
|
-
object_params[link] = value
|
220
|
-
end
|
221
|
-
object_params.delete(:links)
|
222
|
-
end
|
223
|
-
verify_permitted_params(object_params, allowed_params)
|
224
|
-
|
225
|
-
checked_attributes = {}
|
226
|
-
checked_has_one_associations = {}
|
227
|
-
checked_has_many_associations = {}
|
228
|
-
|
229
|
-
object_params.each do |key, value|
|
230
|
-
param = key.to_sym
|
231
|
-
|
232
|
-
association = _associations[param]
|
233
|
-
|
234
|
-
if association.is_a?(JSONAPI::Association::HasOne)
|
235
|
-
checked_has_one_associations[param.to_sym] = resource_for(association.serialize_type_name).verify_key(value, context)
|
236
|
-
elsif association.is_a?(JSONAPI::Association::HasMany)
|
237
|
-
keys = []
|
238
|
-
value.each do |value|
|
239
|
-
keys.push(resource_for(association.serialize_type_name).verify_key(value, context))
|
240
|
-
end
|
241
|
-
checked_has_many_associations[param.to_sym] = keys
|
242
|
-
else
|
243
|
-
checked_attributes[param] = value
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
return {
|
248
|
-
attributes: checked_attributes,
|
249
|
-
has_one: checked_has_one_associations,
|
250
|
-
has_many: checked_has_many_associations
|
251
|
-
}
|
252
|
-
end
|
253
|
-
|
254
233
|
def verify_filters(filters, context = nil)
|
255
234
|
verified_filters = {}
|
256
235
|
filters.each do |filter, raw_value|
|
@@ -261,7 +240,7 @@ module JSONAPI
|
|
261
240
|
end
|
262
241
|
|
263
242
|
def is_filter_association?(filter)
|
264
|
-
filter ==
|
243
|
+
filter == _type || _associations.include?(filter)
|
265
244
|
end
|
266
245
|
|
267
246
|
def verify_filter(filter, raw, context = nil)
|
@@ -291,6 +270,10 @@ module JSONAPI
|
|
291
270
|
end
|
292
271
|
|
293
272
|
# quasi private class methods
|
273
|
+
def _attribute_options(attr)
|
274
|
+
default_attribute_options.merge(@_attributes[attr])
|
275
|
+
end
|
276
|
+
|
294
277
|
def _updateable_associations
|
295
278
|
associations = []
|
296
279
|
|
@@ -303,11 +286,12 @@ module JSONAPI
|
|
303
286
|
end
|
304
287
|
|
305
288
|
def _has_association?(type)
|
306
|
-
|
289
|
+
type = type.to_s
|
290
|
+
@_associations.has_key?(type.singularize.to_sym) || @_associations.has_key?(type.pluralize.to_sym)
|
307
291
|
end
|
308
292
|
|
309
293
|
def _association(type)
|
310
|
-
type = type.to_sym
|
294
|
+
type = type.to_sym
|
311
295
|
@_associations[type]
|
312
296
|
end
|
313
297
|
|
@@ -315,16 +299,12 @@ module JSONAPI
|
|
315
299
|
@_model_name ||= self.name.demodulize.sub(/Resource$/, '')
|
316
300
|
end
|
317
301
|
|
318
|
-
def _serialize_as
|
319
|
-
@_serialize_as ||= self._type
|
320
|
-
end
|
321
|
-
|
322
302
|
def _key
|
323
303
|
@_key ||= :id
|
324
304
|
end
|
325
305
|
|
326
306
|
def _as_parent_key
|
327
|
-
@_as_parent_key ||= "#{
|
307
|
+
@_as_parent_key ||= "#{_type.to_s.singularize}_#{_key}"
|
328
308
|
end
|
329
309
|
|
330
310
|
def _allowed_filters
|
@@ -351,11 +331,7 @@ module JSONAPI
|
|
351
331
|
end
|
352
332
|
|
353
333
|
def _allowed_filter?(filter)
|
354
|
-
_allowed_filters.include?(filter
|
355
|
-
end
|
356
|
-
|
357
|
-
def _validate_field(field)
|
358
|
-
_attributes.include?(field) || _associations.key?(field)
|
334
|
+
_allowed_filters.include?(filter)
|
359
335
|
end
|
360
336
|
|
361
337
|
private
|
@@ -374,7 +350,7 @@ module JSONAPI
|
|
374
350
|
end unless method_defined?(key)
|
375
351
|
|
376
352
|
define_method "_#{attr}_object" do
|
377
|
-
type_name = self.class._associations[attr].
|
353
|
+
type_name = self.class._associations[attr].type
|
378
354
|
resource_class = self.class.resource_for(type_name)
|
379
355
|
if resource_class
|
380
356
|
associated_object = @object.send attr
|
@@ -389,7 +365,7 @@ module JSONAPI
|
|
389
365
|
end unless method_defined?(key)
|
390
366
|
|
391
367
|
define_method "_#{attr}_objects" do
|
392
|
-
type_name = self.class._associations[attr].
|
368
|
+
type_name = self.class._associations[attr].type
|
393
369
|
resource_class = self.class.resource_for(type_name)
|
394
370
|
resources = []
|
395
371
|
if resource_class
|
@@ -403,15 +379,6 @@ module JSONAPI
|
|
403
379
|
end
|
404
380
|
end
|
405
381
|
end
|
406
|
-
|
407
|
-
def verify_permitted_params(params, allowed_param_set)
|
408
|
-
params_not_allowed = []
|
409
|
-
params.keys.each do |key|
|
410
|
-
param = key.to_sym
|
411
|
-
params_not_allowed.push(param) unless allowed_param_set.include?(param)
|
412
|
-
end
|
413
|
-
raise JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
|
414
|
-
end
|
415
382
|
end
|
416
383
|
end
|
417
|
-
end
|
384
|
+
end
|
@@ -15,7 +15,10 @@ module JSONAPI
|
|
15
15
|
|
16
16
|
before_filter {
|
17
17
|
begin
|
18
|
-
@request = JSONAPI::Request.new(
|
18
|
+
@request = JSONAPI::Request.new(params, {
|
19
|
+
context: context,
|
20
|
+
key_formatter: key_formatter
|
21
|
+
})
|
19
22
|
render_errors(@request.errors) unless @request.errors.empty?
|
20
23
|
rescue => e
|
21
24
|
handle_exceptions(e)
|
@@ -23,11 +26,13 @@ module JSONAPI
|
|
23
26
|
}
|
24
27
|
|
25
28
|
def index
|
26
|
-
render json: JSONAPI::ResourceSerializer.new.
|
29
|
+
render json: JSONAPI::ResourceSerializer.new.serialize_to_hash(
|
27
30
|
resource_klass.find(resource_klass.verify_filters(@request.filters, context), context),
|
28
|
-
@request.
|
29
|
-
@request.fields,
|
30
|
-
context
|
31
|
+
include: @request.include,
|
32
|
+
fields: @request.fields,
|
33
|
+
context: context,
|
34
|
+
attribute_formatters: attribute_formatters,
|
35
|
+
key_formatter: key_formatter)
|
31
36
|
rescue => e
|
32
37
|
handle_exceptions(e)
|
33
38
|
end
|
@@ -35,16 +40,22 @@ module JSONAPI
|
|
35
40
|
def show
|
36
41
|
keys = parse_key_array(params[resource_klass._key])
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
if keys.length > 1
|
44
|
+
resources = []
|
45
|
+
keys.each do |key|
|
46
|
+
resources.push(resource_klass.find_by_key(key, context))
|
47
|
+
end
|
48
|
+
else
|
49
|
+
resources = resource_klass.find_by_key(keys[0], context)
|
41
50
|
end
|
42
51
|
|
43
|
-
render json: JSONAPI::ResourceSerializer.new.
|
52
|
+
render json: JSONAPI::ResourceSerializer.new.serialize_to_hash(
|
44
53
|
resources,
|
45
|
-
@request.
|
46
|
-
@request.fields,
|
47
|
-
context
|
54
|
+
include: @request.include,
|
55
|
+
fields: @request.fields,
|
56
|
+
context: context,
|
57
|
+
attribute_formatters: attribute_formatters,
|
58
|
+
key_formatter: key_formatter)
|
48
59
|
rescue => e
|
49
60
|
handle_exceptions(e)
|
50
61
|
end
|
@@ -70,6 +81,10 @@ module JSONAPI
|
|
70
81
|
process_request_operations
|
71
82
|
end
|
72
83
|
|
84
|
+
def update_association
|
85
|
+
process_request_operations
|
86
|
+
end
|
87
|
+
|
73
88
|
def update
|
74
89
|
process_request_operations
|
75
90
|
end
|
@@ -115,6 +130,20 @@ module JSONAPI
|
|
115
130
|
{}
|
116
131
|
end
|
117
132
|
|
133
|
+
# Control by setting in an initializer:
|
134
|
+
# JSONAPI.configuration.json_key_format = :camelized_key
|
135
|
+
#
|
136
|
+
# Override if you want to set a per controller key format.
|
137
|
+
# Must return a class derived from KeyFormatter.
|
138
|
+
def key_formatter
|
139
|
+
JSONAPI.configuration.key_formatter
|
140
|
+
end
|
141
|
+
|
142
|
+
# override to setup custom attribute_formatters
|
143
|
+
def attribute_formatters
|
144
|
+
{}
|
145
|
+
end
|
146
|
+
|
118
147
|
def render_errors(errors, status = :bad_request)
|
119
148
|
render(json: {errors: errors}, status: errors.count == 1 ? errors[0].status : status)
|
120
149
|
end
|
@@ -138,18 +167,17 @@ module JSONAPI
|
|
138
167
|
if errors.count > 0
|
139
168
|
render :status => errors.count == 1 ? errors[0].status : :bad_request, json: {errors: errors}
|
140
169
|
else
|
141
|
-
|
142
|
-
render :status => results[0].code,
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
# end
|
170
|
+
if results.length > 0 && resources.length > 0
|
171
|
+
render :status => results[0].code,
|
172
|
+
json: JSONAPI::ResourceSerializer.new.serialize_to_hash(resources.length > 1 ? resources : resources[0],
|
173
|
+
include: @request.include,
|
174
|
+
fields: @request.fields,
|
175
|
+
context: context,
|
176
|
+
attribute_formatters: attribute_formatters,
|
177
|
+
key_formatter: key_formatter)
|
178
|
+
else
|
179
|
+
render :status => results[0].code, json: nil
|
180
|
+
end
|
153
181
|
end
|
154
182
|
rescue => e
|
155
183
|
handle_exceptions(e)
|
@@ -166,4 +194,4 @@ module JSONAPI
|
|
166
194
|
end
|
167
195
|
end
|
168
196
|
end
|
169
|
-
end
|
197
|
+
end
|
data/lib/jsonapi/resource_for.rb
CHANGED
@@ -7,12 +7,10 @@ module JSONAPI
|
|
7
7
|
module ClassMethods
|
8
8
|
if RUBY_VERSION >= '2.0'
|
9
9
|
def resource_for(type)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
nil
|
15
|
-
end
|
10
|
+
resource_name = JSONAPI::Resource._resource_name_from_type(type)
|
11
|
+
Object.const_get resource_name if resource_name
|
12
|
+
rescue NameError
|
13
|
+
nil
|
16
14
|
end
|
17
15
|
else
|
18
16
|
def resource_for(type)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
class ResourceSerializer
|
3
3
|
|
4
|
-
#
|
4
|
+
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
|
5
5
|
# include:
|
6
6
|
# Purpose: determines which objects will be side loaded with the source objects in a linked section
|
7
7
|
# Example: ['comments','author','comments.tags','author.posts']
|
@@ -9,45 +9,56 @@ module JSONAPI
|
|
9
9
|
# Purpose: determines which fields are serialized for a resource type. This encompasses both attributes and
|
10
10
|
# association ids in the links section for a resource. Fields are global for a resource type.
|
11
11
|
# Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
|
12
|
-
def
|
13
|
-
|
14
|
-
|
12
|
+
def serialize_to_hash(source, options = {})
|
13
|
+
is_resource_collection = source.respond_to?(:to_ary)
|
14
|
+
return {} if source.nil? || (is_resource_collection && source.size == 0)
|
15
|
+
|
16
|
+
@fields = options.fetch(:fields, {})
|
17
|
+
include = options.fetch(:include, [])
|
18
|
+
@context = options.fetch(:context, nil)
|
19
|
+
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
20
|
+
|
15
21
|
@linked_objects = {}
|
16
22
|
|
17
23
|
requested_associations = parse_includes(include)
|
18
24
|
|
19
|
-
if
|
20
|
-
|
21
|
-
@primary_class_name = source[0].class._serialize_as
|
25
|
+
if is_resource_collection
|
26
|
+
@primary_class_name = source[0].class._type
|
22
27
|
else
|
23
|
-
@primary_class_name = source.class.
|
28
|
+
@primary_class_name = source.class._type
|
24
29
|
end
|
25
30
|
|
26
31
|
process_primary(source, requested_associations)
|
27
32
|
|
28
33
|
primary_class_name = @primary_class_name.to_sym
|
29
|
-
primary_hash = {primary_class_name => []}
|
30
34
|
|
31
35
|
linked_hash = {}
|
36
|
+
primary_objects = []
|
32
37
|
@linked_objects.each do |class_name, objects|
|
33
38
|
class_name = class_name.to_sym
|
34
39
|
|
35
|
-
|
40
|
+
linked_objects = []
|
36
41
|
objects.each_value do |object|
|
37
42
|
if object[:primary]
|
38
|
-
|
43
|
+
primary_objects.push(object[:object_hash])
|
39
44
|
else
|
40
|
-
|
45
|
+
linked_objects.push(object[:object_hash])
|
41
46
|
end
|
42
47
|
end
|
43
|
-
linked_hash[class_name] =
|
48
|
+
linked_hash[format_key(class_name)] = linked_objects unless linked_objects.empty?
|
44
49
|
end
|
45
50
|
|
46
|
-
if
|
47
|
-
primary_hash
|
51
|
+
if is_resource_collection
|
52
|
+
primary_hash = {format_key(primary_class_name) => primary_objects}
|
53
|
+
else
|
54
|
+
primary_hash = {format_key(primary_class_name) => primary_objects[0]}
|
48
55
|
end
|
49
56
|
|
50
|
-
|
57
|
+
if linked_hash.size > 0
|
58
|
+
primary_hash.merge({linked: linked_hash})
|
59
|
+
else
|
60
|
+
primary_hash
|
61
|
+
end
|
51
62
|
end
|
52
63
|
|
53
64
|
private
|
@@ -57,7 +68,7 @@ module JSONAPI
|
|
57
68
|
def parse_includes(includes)
|
58
69
|
requested_associations = {}
|
59
70
|
includes.each do |include|
|
60
|
-
include = include.to_s
|
71
|
+
include = include.to_s.underscore
|
61
72
|
|
62
73
|
pos = include.index('.')
|
63
74
|
if pos
|
@@ -112,14 +123,17 @@ module JSONAPI
|
|
112
123
|
end
|
113
124
|
|
114
125
|
def attribute_hash(source)
|
115
|
-
requested = requested_fields(source.class.
|
116
|
-
fields = source.class._attributes.to_a
|
126
|
+
requested = requested_fields(source.class._type)
|
127
|
+
fields = source.class._attributes.keys.to_a
|
117
128
|
unless requested.nil?
|
118
129
|
fields = requested & fields
|
119
130
|
end
|
120
131
|
|
121
132
|
source.fetchable(fields, @context).each_with_object({}) do |name, hash|
|
122
|
-
hash[name] = source.send(name)
|
133
|
+
hash[format_key(name)] = format_value(source.send(name),
|
134
|
+
source.class._attribute_options(name)[:format],
|
135
|
+
source,
|
136
|
+
@context)
|
123
137
|
end
|
124
138
|
end
|
125
139
|
|
@@ -127,7 +141,7 @@ module JSONAPI
|
|
127
141
|
# class's fetchable method
|
128
142
|
def links_hash(source, requested_associations)
|
129
143
|
associations = source.class._associations
|
130
|
-
requested = requested_fields(source.class.
|
144
|
+
requested = requested_fields(source.class._type)
|
131
145
|
fields = associations.keys
|
132
146
|
unless requested.nil?
|
133
147
|
fields = requested & fields
|
@@ -141,7 +155,7 @@ module JSONAPI
|
|
141
155
|
key = association.key
|
142
156
|
|
143
157
|
if field_set.include?(name)
|
144
|
-
hash[name] = source.send(key)
|
158
|
+
hash[format_key(name)] = source.send(key)
|
145
159
|
end
|
146
160
|
|
147
161
|
ia = requested_associations.is_a?(Hash) ? requested_associations[name] : nil
|
@@ -149,7 +163,7 @@ module JSONAPI
|
|
149
163
|
include_linked_object = ia && ia[:include]
|
150
164
|
include_linked_children = ia && ia[:include_children]
|
151
165
|
|
152
|
-
type = association.
|
166
|
+
type = association.type
|
153
167
|
|
154
168
|
# If the object has been serialized once it will be in the related objects list,
|
155
169
|
# but it's possible all children won't have been captured. So we must still go
|
@@ -157,13 +171,14 @@ module JSONAPI
|
|
157
171
|
if include_linked_object || include_linked_children
|
158
172
|
if association.is_a?(JSONAPI::Association::HasOne)
|
159
173
|
object = source.send("_#{name}_object")
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
174
|
+
if object
|
175
|
+
id = object.send(association.primary_key)
|
176
|
+
associations_only = already_serialized?(type, id)
|
177
|
+
if include_linked_object && !associations_only
|
178
|
+
add_linked_object(type, id, object_hash(object, ia[:include_related]))
|
179
|
+
elsif include_linked_children || associations_only
|
180
|
+
links_hash(object, ia[:include_related])
|
181
|
+
end
|
167
182
|
end
|
168
183
|
elsif association.is_a?(JSONAPI::Association::HasMany)
|
169
184
|
objects = source.send("_#{name}_objects")
|
@@ -183,16 +198,20 @@ module JSONAPI
|
|
183
198
|
end
|
184
199
|
|
185
200
|
def already_serialized?(type, id)
|
201
|
+
type = format_key(type)
|
186
202
|
return @linked_objects.key?(type) && @linked_objects[type].key?(id)
|
187
203
|
end
|
188
204
|
|
189
205
|
# Sets that an object should be included in the primary document of the response.
|
190
206
|
def set_primary(type, id)
|
207
|
+
type = format_key(type)
|
191
208
|
@linked_objects[type][id][:primary] = true
|
192
209
|
end
|
193
210
|
|
194
211
|
# Collects the hashes for all objects processed by the serializer
|
195
212
|
def add_linked_object(type, id, object_hash, primary = false)
|
213
|
+
type = format_key(type)
|
214
|
+
|
196
215
|
unless @linked_objects.key?(type)
|
197
216
|
@linked_objects[type] = {}
|
198
217
|
end
|
@@ -205,5 +224,14 @@ module JSONAPI
|
|
205
224
|
@linked_objects[type].store(id, {primary: primary, object_hash: object_hash})
|
206
225
|
end
|
207
226
|
end
|
227
|
+
|
228
|
+
def format_key(key)
|
229
|
+
@key_formatter.format(key)
|
230
|
+
end
|
231
|
+
|
232
|
+
def format_value(value, format, source, context)
|
233
|
+
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
|
234
|
+
value_formatter.format(value, source, context)
|
235
|
+
end
|
208
236
|
end
|
209
|
-
end
|
237
|
+
end
|
data/lib/jsonapi/routing_ext.rb
CHANGED
@@ -47,7 +47,7 @@ module ActionDispatch
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def links_methods(options)
|
50
|
-
default_methods = [:show, :create, :destroy]
|
50
|
+
default_methods = [:show, :create, :destroy, :update]
|
51
51
|
if only = options[:only]
|
52
52
|
Array(only).map(&:to_sym)
|
53
53
|
elsif except = options[:except]
|
@@ -73,6 +73,10 @@ module ActionDispatch
|
|
73
73
|
match "links/#{link_type}", controller: res._type.to_s, action: 'create_association', association: link_type.to_s, via: [:post]
|
74
74
|
end
|
75
75
|
|
76
|
+
if methods.include?(:update)
|
77
|
+
match "links/#{link_type}", controller: res._type.to_s, action: 'update_association', association: link_type.to_s, via: [:put]
|
78
|
+
end
|
79
|
+
|
76
80
|
if methods.include?(:destroy)
|
77
81
|
match "links/#{link_type}", controller: res._type.to_s, action: 'destroy_association', association: link_type.to_s, via: [:delete]
|
78
82
|
end
|
@@ -94,6 +98,10 @@ module ActionDispatch
|
|
94
98
|
match "links/#{link_type}", controller: res._type.to_s, action: 'create_association', association: link_type.to_s, via: [:post]
|
95
99
|
end
|
96
100
|
|
101
|
+
if methods.include?(:update) && res._association(link_type).acts_as_set
|
102
|
+
match "links/#{link_type}", controller: res._type.to_s, action: 'update_association', association: link_type.to_s, via: [:put]
|
103
|
+
end
|
104
|
+
|
97
105
|
if methods.include?(:destroy)
|
98
106
|
match "links/#{link_type}/:keys", controller: res._type.to_s, action: 'destroy_association', association: link_type.to_s, via: [:delete]
|
99
107
|
end
|
data/lib/jsonapi-resources.rb
CHANGED