jsonapi-resources 0.1.1 → 0.2.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/README.md +68 -1
- data/Rakefile +3 -7
- data/lib/jsonapi-resources.rb +1 -0
- data/lib/jsonapi/configuration.rb +29 -4
- data/lib/jsonapi/error_codes.rb +6 -2
- data/lib/jsonapi/exceptions.rb +92 -19
- data/lib/jsonapi/operation.rb +0 -18
- data/lib/jsonapi/paginator.rb +98 -0
- data/lib/jsonapi/request.rb +257 -182
- data/lib/jsonapi/resource.rb +58 -47
- data/lib/jsonapi/resource_controller.rb +85 -29
- data/lib/jsonapi/resource_serializer.rb +88 -33
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +38 -12
- data/test/controllers/controller_test.rb +761 -455
- data/test/fixtures/active_record.rb +90 -18
- data/test/integration/requests/request_test.rb +183 -25
- data/test/integration/routes/routes_test.rb +0 -5
- data/test/test_helper.rb +31 -7
- data/test/unit/operation/operations_processor_test.rb +28 -1
- data/test/unit/resource/resource_test.rb +4 -0
- data/test/unit/serializer/serializer_test.rb +882 -377
- metadata +3 -2
data/lib/jsonapi/resource.rb
CHANGED
@@ -72,12 +72,6 @@ module JSONAPI
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
def create_has_one_link(association_type, association_key_value)
|
76
|
-
change :create_has_one_link do
|
77
|
-
_create_has_one_link(association_type, association_key_value)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
75
|
def replace_has_one_link(association_type, association_key_value)
|
82
76
|
change :replace_has_one_link do
|
83
77
|
_replace_has_one_link(association_type, association_key_value)
|
@@ -148,19 +142,6 @@ module JSONAPI
|
|
148
142
|
@save_needed = true
|
149
143
|
end
|
150
144
|
|
151
|
-
def _create_has_one_link(association_type, association_key_value)
|
152
|
-
association = self.class._associations[association_type]
|
153
|
-
|
154
|
-
# ToDo: Add option to skip relations that already exist instead of returning an error?
|
155
|
-
relation = @model.send("#{association.foreign_key}")
|
156
|
-
if relation.nil?
|
157
|
-
send("#{association.foreign_key}=", association_key_value)
|
158
|
-
else
|
159
|
-
raise JSONAPI::Exceptions::HasOneRelationExists.new
|
160
|
-
end
|
161
|
-
@save_needed = true
|
162
|
-
end
|
163
|
-
|
164
145
|
def _replace_has_one_link(association_type, association_key_value)
|
165
146
|
association = self.class._associations[association_type]
|
166
147
|
|
@@ -215,6 +196,8 @@ module JSONAPI
|
|
215
196
|
type = base.name.demodulize.sub(/Resource$/, '').underscore
|
216
197
|
base._type = type.pluralize.to_sym
|
217
198
|
|
199
|
+
base.attribute :id, format: :id
|
200
|
+
|
218
201
|
check_reserved_resource_name(base._type, base.name)
|
219
202
|
|
220
203
|
# If eager loading is on this is how all the resource types are setup
|
@@ -223,7 +206,7 @@ module JSONAPI
|
|
223
206
|
@@resource_types[base._type] ||= base.name.demodulize
|
224
207
|
end
|
225
208
|
|
226
|
-
attr_accessor :_attributes, :_associations, :_allowed_filters , :_type
|
209
|
+
attr_accessor :_attributes, :_associations, :_allowed_filters , :_type, :_paginator
|
227
210
|
|
228
211
|
def create(context)
|
229
212
|
self.new(self.create_model, context)
|
@@ -251,6 +234,7 @@ module JSONAPI
|
|
251
234
|
def attribute(attr, options = {})
|
252
235
|
check_reserved_attribute_name(attr)
|
253
236
|
|
237
|
+
@_attributes ||= {}
|
254
238
|
@_attributes[attr] = options
|
255
239
|
define_method attr do
|
256
240
|
@model.send(attr)
|
@@ -298,7 +282,7 @@ module JSONAPI
|
|
298
282
|
|
299
283
|
# Override in your resource to filter the updateable keys
|
300
284
|
def updateable_fields(context = nil)
|
301
|
-
_updateable_associations | _attributes.keys
|
285
|
+
_updateable_associations | _attributes.keys - [_primary_key]
|
302
286
|
end
|
303
287
|
|
304
288
|
# Override in your resource to filter the createable keys
|
@@ -315,22 +299,27 @@ module JSONAPI
|
|
315
299
|
_associations.keys | _attributes.keys
|
316
300
|
end
|
317
301
|
|
318
|
-
def
|
319
|
-
|
302
|
+
def apply_pagination(records, paginator)
|
303
|
+
if paginator
|
304
|
+
records = paginator.apply(records)
|
305
|
+
end
|
306
|
+
records
|
320
307
|
end
|
321
308
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
sort_params = options.fetch(:sort_params) { [] }
|
326
|
-
includes = []
|
309
|
+
def apply_sort(records, order_options)
|
310
|
+
records.order(order_options)
|
311
|
+
end
|
327
312
|
|
328
|
-
|
313
|
+
def apply_filter(records, filter, value)
|
314
|
+
records.where(filter => value)
|
315
|
+
end
|
329
316
|
|
317
|
+
def apply_filters(records, filters)
|
318
|
+
required_includes = []
|
330
319
|
filters.each do |filter, value|
|
331
320
|
if _associations.include?(filter)
|
332
321
|
if _associations[filter].is_a?(JSONAPI::Association::HasMany)
|
333
|
-
|
322
|
+
required_includes.push(filter)
|
334
323
|
records = apply_filter(records, "#{filter}.#{_associations[filter].primary_key}", value)
|
335
324
|
else
|
336
325
|
records = apply_filter(records, "#{_associations[filter].foreign_key}", value)
|
@@ -339,10 +328,22 @@ module JSONAPI
|
|
339
328
|
records = apply_filter(records, filter, value)
|
340
329
|
end
|
341
330
|
end
|
331
|
+
records.includes(required_includes)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Override this method if you have more complex requirements than this basic find method provides
|
335
|
+
def find(filters, options = {})
|
336
|
+
context = options[:context]
|
337
|
+
sort_criteria = options.fetch(:sort_criteria) { [] }
|
342
338
|
|
343
339
|
resources = []
|
344
|
-
|
345
|
-
records
|
340
|
+
|
341
|
+
records = records(options)
|
342
|
+
records = apply_filters(records, filters)
|
343
|
+
records = apply_sort(records, construct_order_options(sort_criteria))
|
344
|
+
records = apply_pagination(records, options[:paginator])
|
345
|
+
|
346
|
+
records.each do |model|
|
346
347
|
resources.push self.new(model, context)
|
347
348
|
end
|
348
349
|
|
@@ -480,6 +481,14 @@ module JSONAPI
|
|
480
481
|
return class_name
|
481
482
|
end
|
482
483
|
|
484
|
+
def _paginator
|
485
|
+
@_paginator ||= JSONAPI.configuration.default_paginator
|
486
|
+
end
|
487
|
+
|
488
|
+
def paginator(paginator)
|
489
|
+
@_paginator = paginator
|
490
|
+
end
|
491
|
+
|
483
492
|
# :nocov:
|
484
493
|
if RUBY_VERSION >= '2.0'
|
485
494
|
def _model_class
|
@@ -500,8 +509,13 @@ module JSONAPI
|
|
500
509
|
@module_path ||= self.name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').downcase : ''
|
501
510
|
end
|
502
511
|
|
503
|
-
|
512
|
+
def construct_order_options(sort_params)
|
513
|
+
sort_params.each_with_object({}) { |sort, order_hash|
|
514
|
+
order_hash[sort[:field]] = sort[:direction]
|
515
|
+
}
|
516
|
+
end
|
504
517
|
|
518
|
+
private
|
505
519
|
def check_reserved_resource_name(type, name)
|
506
520
|
if [:ids, :types, :hrefs, :links].include?(type)
|
507
521
|
warn "[NAME COLLISION] `#{name}` is a reserved resource name."
|
@@ -551,14 +565,21 @@ module JSONAPI
|
|
551
565
|
end
|
552
566
|
end unless method_defined?(attr)
|
553
567
|
elsif @_associations[attr].is_a?(JSONAPI::Association::HasMany)
|
554
|
-
define_method attr do
|
568
|
+
define_method attr do |options = {}|
|
555
569
|
type_name = self.class._associations[attr].type.to_s
|
556
570
|
resource_class = self.class.resource_for(self.class.module_path + type_name)
|
571
|
+
filters = options.fetch(:filters, {})
|
572
|
+
sort_criteria = options.fetch(:sort_criteria, {})
|
573
|
+
paginator = options.fetch(:paginator, nil)
|
574
|
+
|
557
575
|
resources = []
|
558
576
|
if resource_class
|
559
|
-
|
560
|
-
|
561
|
-
|
577
|
+
records = @model.send attr
|
578
|
+
records = self.class.apply_filters(records, filters)
|
579
|
+
records = self.class.apply_sort(records, self.class.construct_order_options(sort_criteria))
|
580
|
+
records = self.class.apply_pagination(records, paginator)
|
581
|
+
records.each do |record|
|
582
|
+
resources.push resource_class.new(record, @context)
|
562
583
|
end
|
563
584
|
end
|
564
585
|
return resources
|
@@ -566,16 +587,6 @@ module JSONAPI
|
|
566
587
|
end
|
567
588
|
end
|
568
589
|
end
|
569
|
-
|
570
|
-
def construct_order_options(sort_params)
|
571
|
-
sort_params.each_with_object({}) { |sort_key, order_hash|
|
572
|
-
if sort_key.starts_with?('-')
|
573
|
-
order_hash[sort_key.slice(1..-1)] = :desc
|
574
|
-
else
|
575
|
-
order_hash[sort_key] = :asc
|
576
|
-
end
|
577
|
-
}
|
578
|
-
end
|
579
590
|
end
|
580
591
|
end
|
581
592
|
end
|
@@ -18,32 +18,40 @@ module JSONAPI
|
|
18
18
|
after_filter :setup_response
|
19
19
|
|
20
20
|
def index
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
serializer = JSONAPI::ResourceSerializer.new(resource_klass,
|
22
|
+
include: @request.include,
|
23
|
+
fields: @request.fields,
|
24
|
+
base_url: base_url,
|
25
|
+
key_formatter: key_formatter,
|
26
|
+
route_formatter: route_formatter)
|
27
|
+
|
28
|
+
resource_records = resource_klass.find(resource_klass.verify_filters(@request.filters, context),
|
29
|
+
context: context,
|
30
|
+
sort_criteria: @request.sort_criteria,
|
31
|
+
paginator: @request.paginator)
|
32
|
+
|
33
|
+
render json: serializer.serialize_to_hash(resource_records)
|
28
34
|
rescue => e
|
29
35
|
handle_exceptions(e)
|
30
36
|
end
|
31
37
|
|
32
38
|
def show
|
39
|
+
serializer = JSONAPI::ResourceSerializer.new(resource_klass,
|
40
|
+
include: @request.include,
|
41
|
+
fields: @request.fields,
|
42
|
+
base_url: base_url,
|
43
|
+
key_formatter: key_formatter,
|
44
|
+
route_formatter: route_formatter)
|
45
|
+
|
33
46
|
keys = parse_key_array(params[resource_klass._primary_key])
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
render json:
|
42
|
-
resources,
|
43
|
-
include: @request.include,
|
44
|
-
fields: @request.fields,
|
45
|
-
attribute_formatters: attribute_formatters,
|
46
|
-
key_formatter: key_formatter)
|
48
|
+
resource_records = if keys.length > 1
|
49
|
+
resource_klass.find_by_keys(keys, context: context)
|
50
|
+
else
|
51
|
+
resource_klass.find_by_key(keys[0], context: context)
|
52
|
+
end
|
53
|
+
|
54
|
+
render json: serializer.serialize_to_hash(resource_records)
|
47
55
|
rescue => e
|
48
56
|
handle_exceptions(e)
|
49
57
|
end
|
@@ -56,7 +64,14 @@ module JSONAPI
|
|
56
64
|
parent_resource = resource_klass.find_by_key(parent_key, context: context)
|
57
65
|
|
58
66
|
association = resource_klass._association(association_type)
|
59
|
-
|
67
|
+
|
68
|
+
serializer = JSONAPI::ResourceSerializer.new(resource_klass,
|
69
|
+
fields: @request.fields,
|
70
|
+
base_url: base_url,
|
71
|
+
key_formatter: key_formatter,
|
72
|
+
route_formatter: route_formatter)
|
73
|
+
|
74
|
+
render json: serializer.serialize_to_links_hash(parent_resource, association)
|
60
75
|
rescue => e
|
61
76
|
# :nocov:
|
62
77
|
handle_exceptions(e)
|
@@ -87,6 +102,41 @@ module JSONAPI
|
|
87
102
|
process_request_operations
|
88
103
|
end
|
89
104
|
|
105
|
+
def get_related_resource
|
106
|
+
association_type = params[:association]
|
107
|
+
source_resource = @request.source_klass.find_by_key(@request.source_id, context: context)
|
108
|
+
|
109
|
+
serializer = JSONAPI::ResourceSerializer.new(@request.source_klass,
|
110
|
+
include: @request.include,
|
111
|
+
fields: @request.fields,
|
112
|
+
base_url: base_url,
|
113
|
+
key_formatter: key_formatter,
|
114
|
+
route_formatter: route_formatter)
|
115
|
+
|
116
|
+
render json: serializer.serialize_to_hash(source_resource.send(association_type))
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_related_resources
|
120
|
+
association_type = params[:association]
|
121
|
+
source_resource = @request.source_klass.find_by_key(@request.source_id, context: context)
|
122
|
+
|
123
|
+
related_resources = source_resource.send(association_type,
|
124
|
+
{
|
125
|
+
filters: @request.source_klass.verify_filters(@request.filters, context),
|
126
|
+
sort_criteria: @request.sort_criteria,
|
127
|
+
paginator: @request.paginator
|
128
|
+
})
|
129
|
+
|
130
|
+
serializer = JSONAPI::ResourceSerializer.new(@request.source_klass,
|
131
|
+
include: @request.include,
|
132
|
+
fields: @request.fields,
|
133
|
+
base_url: base_url,
|
134
|
+
key_formatter: key_formatter,
|
135
|
+
route_formatter: route_formatter)
|
136
|
+
|
137
|
+
render json: serializer.serialize_to_hash(related_resources)
|
138
|
+
end
|
139
|
+
|
90
140
|
# Override this to use another operations processor
|
91
141
|
def create_operations_processor
|
92
142
|
JSONAPI::ActiveRecordOperationsProcessor.new
|
@@ -105,6 +155,10 @@ module JSONAPI
|
|
105
155
|
end
|
106
156
|
# :nocov:
|
107
157
|
|
158
|
+
def base_url
|
159
|
+
@base_url ||= request.protocol + request.host_with_port
|
160
|
+
end
|
161
|
+
|
108
162
|
def resource_klass_name
|
109
163
|
@resource_klass_name ||= "#{self.class.name.sub(/Controller$/, '').singularize}Resource"
|
110
164
|
end
|
@@ -149,6 +203,7 @@ module JSONAPI
|
|
149
203
|
|
150
204
|
# Control by setting in an initializer:
|
151
205
|
# JSONAPI.configuration.json_key_format = :camelized_key
|
206
|
+
# JSONAPI.configuration.route = :camelized_route
|
152
207
|
#
|
153
208
|
# Override if you want to set a per controller key format.
|
154
209
|
# Must return a class derived from KeyFormatter.
|
@@ -156,9 +211,8 @@ module JSONAPI
|
|
156
211
|
JSONAPI.configuration.key_formatter
|
157
212
|
end
|
158
213
|
|
159
|
-
|
160
|
-
|
161
|
-
{}
|
214
|
+
def route_formatter
|
215
|
+
JSONAPI.configuration.route_formatter
|
162
216
|
end
|
163
217
|
|
164
218
|
def render_errors(errors)
|
@@ -185,13 +239,15 @@ module JSONAPI
|
|
185
239
|
render status: errors[0].status, json: {errors: errors}
|
186
240
|
else
|
187
241
|
if results.length > 0 && resources.length > 0
|
242
|
+
serializer = JSONAPI::ResourceSerializer.new(resource_klass,
|
243
|
+
include: @request.include,
|
244
|
+
fields: @request.fields,
|
245
|
+
base_url: base_url,
|
246
|
+
key_formatter: key_formatter,
|
247
|
+
route_formatter: route_formatter)
|
248
|
+
|
188
249
|
render status: results[0].code,
|
189
|
-
json:
|
190
|
-
resources.length > 1 ? resources : resources[0],
|
191
|
-
include: @request.include,
|
192
|
-
fields: @request.fields,
|
193
|
-
attribute_formatters: attribute_formatters,
|
194
|
-
key_formatter: key_formatter)
|
250
|
+
json: serializer.serialize_to_hash(resources.length > 1 ? resources : resources[0])
|
195
251
|
else
|
196
252
|
render status: results[0].code, json: nil
|
197
253
|
end
|
@@ -1,12 +1,7 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
class ResourceSerializer
|
3
3
|
|
4
|
-
|
5
|
-
@primary_resource_klass = primary_resource_klass
|
6
|
-
@primary_class_name = @primary_resource_klass._type
|
7
|
-
end
|
8
|
-
|
9
|
-
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
|
4
|
+
# Options can include
|
10
5
|
# include:
|
11
6
|
# Purpose: determines which objects will be side loaded with the source objects in a linked section
|
12
7
|
# Example: ['comments','author','comments.tags','author.posts']
|
@@ -14,26 +9,33 @@ module JSONAPI
|
|
14
9
|
# Purpose: determines which fields are serialized for a resource type. This encompasses both attributes and
|
15
10
|
# association ids in the links section for a resource. Fields are global for a resource type.
|
16
11
|
# Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
|
17
|
-
|
18
|
-
|
12
|
+
# key_formatter: KeyFormatter class to override the default configuration
|
13
|
+
# base_url: a string to prepend to generated resource links
|
19
14
|
|
20
|
-
|
21
|
-
|
15
|
+
def initialize(primary_resource_klass, options = {})
|
16
|
+
@primary_resource_klass = primary_resource_klass
|
17
|
+
@primary_class_name = @primary_resource_klass._type
|
22
18
|
|
19
|
+
@fields = options.fetch(:fields, {})
|
20
|
+
@include = options.fetch(:include, [])
|
23
21
|
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
22
|
+
@route_formatter = options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
|
23
|
+
@base_url = options.fetch(:base_url, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
|
27
|
+
def serialize_to_hash(source)
|
28
|
+
is_resource_collection = source.respond_to?(:to_ary)
|
24
29
|
|
25
30
|
@linked_objects = {}
|
26
31
|
|
27
|
-
requested_associations = parse_includes(include)
|
32
|
+
requested_associations = parse_includes(@include)
|
28
33
|
|
29
34
|
process_primary(source, requested_associations)
|
30
35
|
|
31
|
-
|
36
|
+
linked_objects = []
|
32
37
|
primary_objects = []
|
33
|
-
@linked_objects.
|
34
|
-
class_name = class_name.to_sym
|
35
|
-
|
36
|
-
linked_objects = []
|
38
|
+
@linked_objects.each_value do |objects|
|
37
39
|
objects.each_value do |object|
|
38
40
|
if object[:primary]
|
39
41
|
primary_objects.push(object[:object_hash])
|
@@ -41,20 +43,20 @@ module JSONAPI
|
|
41
43
|
linked_objects.push(object[:object_hash])
|
42
44
|
end
|
43
45
|
end
|
44
|
-
linked_hash[format_key(class_name)] = linked_objects unless linked_objects.empty?
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
|
-
primary_hash = {format_key(@primary_class_name) => primary_objects}
|
49
|
-
else
|
50
|
-
primary_hash = {format_key(@primary_class_name) => primary_objects[0]}
|
51
|
-
end
|
48
|
+
primary_hash = {data: is_resource_collection ? primary_objects : primary_objects[0]}
|
52
49
|
|
53
|
-
if
|
54
|
-
primary_hash
|
50
|
+
if linked_objects.size > 0
|
51
|
+
primary_hash[:linked] = linked_objects
|
55
52
|
else
|
56
53
|
primary_hash
|
57
54
|
end
|
55
|
+
primary_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def serialize_to_links_hash(source, requested_association)
|
59
|
+
{data: link_object(source, requested_association, true)}
|
58
60
|
end
|
59
61
|
|
60
62
|
private
|
@@ -111,6 +113,10 @@ module JSONAPI
|
|
111
113
|
def object_hash(source, requested_associations)
|
112
114
|
obj_hash = attribute_hash(source)
|
113
115
|
links = links_hash(source, requested_associations)
|
116
|
+
|
117
|
+
# ToDo: Do we format these required keys
|
118
|
+
obj_hash[format_key('type')] = format_value(source.class._type.to_s, :default, source)
|
119
|
+
obj_hash[format_key('id')] ||= format_value(source.id, :id, source)
|
114
120
|
obj_hash.merge!({links: links}) unless links.empty?
|
115
121
|
return obj_hash
|
116
122
|
end
|
@@ -152,30 +158,33 @@ module JSONAPI
|
|
152
158
|
field_set = Set.new(fields)
|
153
159
|
|
154
160
|
included_associations = source.fetchable_fields & associations.keys
|
155
|
-
associations.each_with_object({}) do |(name, association), hash|
|
156
|
-
if included_associations.include? name
|
157
161
|
|
158
|
-
|
159
|
-
|
160
|
-
end
|
162
|
+
links = {}
|
163
|
+
links[:self] = self_href(source)
|
161
164
|
|
165
|
+
associations.each_with_object(links) do |(name, association), hash|
|
166
|
+
if included_associations.include? name
|
162
167
|
ia = requested_associations.is_a?(Hash) ? requested_associations[name] : nil
|
163
168
|
|
164
|
-
|
169
|
+
include_linkage = ia && ia[:include]
|
165
170
|
include_linked_children = ia && ia[:include_children]
|
166
171
|
|
172
|
+
if field_set.include?(name)
|
173
|
+
hash[format_key(name)] = link_object(source, association, include_linkage)
|
174
|
+
end
|
175
|
+
|
167
176
|
type = association.type
|
168
177
|
|
169
178
|
# If the object has been serialized once it will be in the related objects list,
|
170
179
|
# but it's possible all children won't have been captured. So we must still go
|
171
180
|
# through the associations.
|
172
|
-
if
|
181
|
+
if include_linkage || include_linked_children
|
173
182
|
if association.is_a?(JSONAPI::Association::HasOne)
|
174
183
|
resource = source.send(name)
|
175
184
|
if resource
|
176
185
|
id = resource.id
|
177
186
|
associations_only = already_serialized?(type, id)
|
178
|
-
if
|
187
|
+
if include_linkage && !associations_only
|
179
188
|
add_linked_object(type, id, object_hash(resource, ia[:include_related]))
|
180
189
|
elsif include_linked_children || associations_only
|
181
190
|
links_hash(resource, ia[:include_related])
|
@@ -186,7 +195,7 @@ module JSONAPI
|
|
186
195
|
resources.each do |resource|
|
187
196
|
id = resource.id
|
188
197
|
associations_only = already_serialized?(type, id)
|
189
|
-
if
|
198
|
+
if include_linkage && !associations_only
|
190
199
|
add_linked_object(type, id, object_hash(resource, ia[:include_related]))
|
191
200
|
elsif include_linked_children || associations_only
|
192
201
|
links_hash(resource, ia[:include_related])
|
@@ -198,11 +207,57 @@ module JSONAPI
|
|
198
207
|
end
|
199
208
|
end
|
200
209
|
|
210
|
+
def formatted_module_path(source)
|
211
|
+
source.class.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : ''
|
212
|
+
end
|
213
|
+
|
214
|
+
def self_href(source)
|
215
|
+
"#{@base_url}/#{formatted_module_path(source)}#{@route_formatter.format(source.class._type.to_s)}/#{source.id}"
|
216
|
+
end
|
217
|
+
|
201
218
|
def already_serialized?(type, id)
|
202
219
|
type = format_key(type)
|
203
220
|
return @linked_objects.key?(type) && @linked_objects[type].key?(id)
|
204
221
|
end
|
205
222
|
|
223
|
+
def format_route(route)
|
224
|
+
@route_formatter.format(route.to_s)
|
225
|
+
end
|
226
|
+
|
227
|
+
def link_object_has_one(source, association)
|
228
|
+
route = association.name
|
229
|
+
|
230
|
+
link_object_hash = {}
|
231
|
+
link_object_hash[:self] = "#{self_href(source)}/links/#{format_route(route)}"
|
232
|
+
link_object_hash[:resource] = "#{self_href(source)}/#{format_route(route)}"
|
233
|
+
# ToDo: Get correct formatting figured out
|
234
|
+
link_object_hash[:type] = format_route(association.type)
|
235
|
+
link_object_hash[:id] = foreign_key_value(source, association)
|
236
|
+
link_object_hash
|
237
|
+
end
|
238
|
+
|
239
|
+
def link_object_has_many(source, association, include_linkage)
|
240
|
+
route = association.name
|
241
|
+
|
242
|
+
link_object_hash = {}
|
243
|
+
link_object_hash[:self] = "#{self_href(source)}/links/#{format_route(route)}"
|
244
|
+
link_object_hash[:resource] = "#{self_href(source)}/#{format_route(route)}"
|
245
|
+
if include_linkage
|
246
|
+
# ToDo: Get correct formatting figured out
|
247
|
+
link_object_hash[:type] = format_route(association.type)
|
248
|
+
link_object_hash[:ids] = foreign_key_value(source, association)
|
249
|
+
end
|
250
|
+
link_object_hash
|
251
|
+
end
|
252
|
+
|
253
|
+
def link_object(source, association, include_linkage = false)
|
254
|
+
if association.is_a?(JSONAPI::Association::HasOne)
|
255
|
+
link_object_has_one(source, association)
|
256
|
+
elsif association.is_a?(JSONAPI::Association::HasMany)
|
257
|
+
link_object_has_many(source, association, include_linkage)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
206
261
|
# Extracts the foreign key value for an association.
|
207
262
|
def foreign_key_value(source, association)
|
208
263
|
foreign_key = association.foreign_key
|