jsonapi-resources 0.0.4 → 0.0.5
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 +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