jsonapi-resources 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|