sanger-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/LICENSE.txt +1 -1
- data/README.md +35 -12
- data/lib/bug_report_templates/rails_5_latest.rb +125 -0
- data/lib/bug_report_templates/rails_5_master.rb +140 -0
- data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +26 -0
- data/lib/jsonapi/active_relation/join_manager.rb +297 -0
- data/lib/jsonapi/active_relation_resource.rb +898 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +130 -113
- data/lib/jsonapi/basic_resource.rb +1164 -0
- data/lib/jsonapi/cached_response_fragment.rb +129 -0
- data/lib/jsonapi/callbacks.rb +2 -0
- data/lib/jsonapi/compatibility_helper.rb +29 -0
- data/lib/jsonapi/compiled_json.rb +13 -1
- data/lib/jsonapi/configuration.rb +88 -21
- data/lib/jsonapi/error.rb +29 -0
- data/lib/jsonapi/error_codes.rb +4 -0
- data/lib/jsonapi/exceptions.rb +82 -50
- data/lib/jsonapi/formatter.rb +5 -3
- data/lib/jsonapi/include_directives.rb +22 -67
- data/lib/jsonapi/link_builder.rb +76 -80
- data/lib/jsonapi/mime_types.rb +6 -10
- data/lib/jsonapi/naive_cache.rb +2 -0
- data/lib/jsonapi/operation.rb +18 -5
- data/lib/jsonapi/operation_result.rb +76 -16
- data/lib/jsonapi/paginator.rb +2 -0
- data/lib/jsonapi/path.rb +45 -0
- data/lib/jsonapi/path_segment.rb +78 -0
- data/lib/jsonapi/processor.rb +193 -115
- data/lib/jsonapi/relationship.rb +145 -14
- data/lib/jsonapi/request.rb +734 -0
- data/lib/jsonapi/resource.rb +3 -1251
- data/lib/jsonapi/resource_controller.rb +2 -0
- data/lib/jsonapi/resource_controller_metal.rb +7 -1
- data/lib/jsonapi/resource_fragment.rb +56 -0
- data/lib/jsonapi/resource_identity.rb +44 -0
- data/lib/jsonapi/resource_serializer.rb +158 -284
- data/lib/jsonapi/resource_set.rb +196 -0
- data/lib/jsonapi/resource_tree.rb +236 -0
- data/lib/jsonapi/resources/railtie.rb +9 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +107 -83
- data/lib/jsonapi/routing_ext.rb +50 -26
- data/lib/jsonapi-resources.rb +23 -5
- data/lib/tasks/check_upgrade.rake +52 -0
- metadata +43 -31
- data/lib/jsonapi/cached_resource_fragment.rb +0 -127
- data/lib/jsonapi/operation_dispatcher.rb +0 -88
- data/lib/jsonapi/operation_results.rb +0 -35
- data/lib/jsonapi/relationship_builder.rb +0 -167
- data/lib/jsonapi/request_parser.rb +0 -678
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
class ResourceSerializer
|
3
5
|
|
4
|
-
attr_reader :link_builder, :key_formatter, :serialization_options,
|
6
|
+
attr_reader :link_builder, :key_formatter, :serialization_options,
|
5
7
|
:fields, :include_directives, :always_include_to_one_linkage_data,
|
6
|
-
:always_include_to_many_linkage_data
|
8
|
+
:always_include_to_many_linkage_data, :options
|
7
9
|
|
8
10
|
# initialize
|
9
11
|
# Options can include
|
@@ -18,12 +20,12 @@ module JSONAPI
|
|
18
20
|
# serialization_options: additional options that will be passed to resource meta and links lambdas
|
19
21
|
|
20
22
|
def initialize(primary_resource_klass, options = {})
|
23
|
+
@options = options
|
21
24
|
@primary_resource_klass = primary_resource_klass
|
22
|
-
@primary_class_name = primary_resource_klass._type
|
23
25
|
@fields = options.fetch(:fields, {})
|
24
26
|
@include = options.fetch(:include, [])
|
25
|
-
@include_directives = options
|
26
|
-
|
27
|
+
@include_directives = options.fetch(:include_directives,
|
28
|
+
JSONAPI::IncludeDirectives.new(@primary_resource_klass, @include))
|
27
29
|
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
28
30
|
@id_formatter = ValueFormatter.value_formatter_for(:id)
|
29
31
|
@link_builder = generate_link_builder(primary_resource_klass, options)
|
@@ -44,76 +46,92 @@ module JSONAPI
|
|
44
46
|
|
45
47
|
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
|
46
48
|
def serialize_to_hash(source)
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
include_related = include_directives[:include_related]
|
50
|
+
resource_set = JSONAPI::ResourceSet.new(source, include_related, options)
|
51
|
+
resource_set.populate!(self, options[:context], options)
|
50
52
|
|
51
|
-
|
53
|
+
if source.is_a?(Array)
|
54
|
+
serialize_resource_set_to_hash_plural(resource_set)
|
55
|
+
else
|
56
|
+
serialize_resource_set_to_hash_single(resource_set)
|
57
|
+
end
|
58
|
+
end
|
52
59
|
|
53
|
-
|
60
|
+
# Converts a resource_set to a hash, conforming to the JSONAPI structure
|
61
|
+
def serialize_resource_set_to_hash_single(resource_set)
|
54
62
|
|
55
63
|
primary_objects = []
|
64
|
+
included_objects = []
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
else
|
69
|
-
if source.try(:id)
|
70
|
-
case source
|
71
|
-
when CachedResourceFragment then primary_objects.push(@included_objects[source.type][source.id][:object_hash])
|
72
|
-
when Resource then primary_objects.push(@included_objects[source.class._type][source.id][:object_hash])
|
73
|
-
else raise "Unknown source type #{source.inspect}"
|
66
|
+
resource_set.resource_klasses.each_value do |resource_klass|
|
67
|
+
resource_klass.each_value do |resource|
|
68
|
+
serialized_resource = object_hash(resource[:resource], resource[:relationships])
|
69
|
+
|
70
|
+
if resource[:primary]
|
71
|
+
primary_objects.push(serialized_resource)
|
72
|
+
else
|
73
|
+
included_objects.push(serialized_resource)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
fail "Too many primary objects for show" if (primary_objects.count > 1)
|
79
|
+
primary_hash = { 'data' => primary_objects[0] }
|
80
|
+
|
81
|
+
primary_hash['included'] = included_objects if included_objects.size > 0
|
82
|
+
primary_hash
|
83
|
+
end
|
84
|
+
|
85
|
+
def serialize_resource_set_to_hash_plural(resource_set)
|
86
|
+
|
87
|
+
primary_objects = []
|
78
88
|
included_objects = []
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
89
|
+
|
90
|
+
resource_set.resource_klasses.each_value do |resource_klass|
|
91
|
+
resource_klass.each_value do |resource|
|
92
|
+
serialized_resource = object_hash(resource[:resource], resource[:relationships])
|
93
|
+
|
94
|
+
if resource[:primary]
|
95
|
+
primary_objects.push(serialized_resource)
|
96
|
+
else
|
97
|
+
included_objects.push(serialized_resource)
|
83
98
|
end
|
84
99
|
end
|
85
100
|
end
|
86
101
|
|
87
|
-
primary_hash = { data
|
102
|
+
primary_hash = { 'data' => primary_objects }
|
88
103
|
|
89
|
-
primary_hash[
|
104
|
+
primary_hash['included'] = included_objects if included_objects.size > 0
|
90
105
|
primary_hash
|
91
106
|
end
|
92
107
|
|
93
|
-
def
|
108
|
+
def serialize_related_resource_set_to_hash_plural(resource_set, _source_resource)
|
109
|
+
return serialize_resource_set_to_hash_plural(resource_set)
|
110
|
+
end
|
111
|
+
|
112
|
+
def serialize_to_relationship_hash(source, requested_relationship, resource_ids)
|
94
113
|
if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
|
95
|
-
data = to_one_linkage(
|
114
|
+
data = to_one_linkage(resource_ids[0])
|
96
115
|
else
|
97
|
-
data = to_many_linkage(
|
116
|
+
data = to_many_linkage(resource_ids)
|
98
117
|
end
|
99
118
|
|
100
|
-
{
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
},
|
105
|
-
data: data
|
106
|
-
}
|
107
|
-
end
|
119
|
+
rel_hash = { 'data': data }
|
120
|
+
|
121
|
+
links = default_relationship_links(source, requested_relationship)
|
122
|
+
rel_hash['links'] = links unless links.blank?
|
108
123
|
|
109
|
-
|
110
|
-
link_builder.query_link(query_params)
|
124
|
+
rel_hash
|
111
125
|
end
|
112
126
|
|
113
127
|
def format_key(key)
|
114
128
|
@key_formatter.format(key)
|
115
129
|
end
|
116
130
|
|
131
|
+
def unformat_key(key)
|
132
|
+
@key_formatter.unformat(key)
|
133
|
+
end
|
134
|
+
|
117
135
|
def format_value(value, format)
|
118
136
|
@value_formatter_type_cache.get(format).format(value)
|
119
137
|
end
|
@@ -129,35 +147,38 @@ module JSONAPI
|
|
129
147
|
def config_description(resource_klass)
|
130
148
|
{
|
131
149
|
class_name: self.class.name,
|
132
|
-
|
150
|
+
serialization_options: serialization_options.sort.map(&:as_json),
|
133
151
|
supplying_attribute_fields: supplying_attribute_fields(resource_klass).sort,
|
134
152
|
supplying_relationship_fields: supplying_relationship_fields(resource_klass).sort,
|
135
153
|
link_builder_base_url: link_builder.base_url,
|
136
|
-
route_formatter_class: link_builder.route_formatter.uncached.class.name,
|
137
154
|
key_formatter_class: key_formatter.uncached.class.name,
|
138
155
|
always_include_to_one_linkage_data: always_include_to_one_linkage_data,
|
139
156
|
always_include_to_many_linkage_data: always_include_to_many_linkage_data
|
140
157
|
}
|
141
158
|
end
|
142
159
|
|
143
|
-
|
144
|
-
def object_hash(source, include_directives = {})
|
160
|
+
def object_hash(source, relationship_data)
|
145
161
|
obj_hash = {}
|
146
162
|
|
147
|
-
if source.
|
148
|
-
|
163
|
+
return obj_hash if source.nil?
|
164
|
+
|
165
|
+
fetchable_fields = Set.new(source.fetchable_fields)
|
166
|
+
|
167
|
+
if source.is_a?(JSONAPI::CachedResponseFragment)
|
168
|
+
id_format = source.resource_klass._attribute_options(:id)[:format]
|
169
|
+
|
170
|
+
id_format = 'id' if id_format == :default
|
171
|
+
obj_hash['id'] = format_value(source.id, id_format)
|
149
172
|
obj_hash['type'] = source.type
|
150
173
|
|
151
174
|
obj_hash['links'] = source.links_json if source.links_json
|
152
175
|
obj_hash['attributes'] = source.attributes_json if source.attributes_json
|
153
176
|
|
154
|
-
relationships = cached_relationships_hash(source,
|
155
|
-
obj_hash['relationships'] = relationships unless relationships.
|
177
|
+
relationships = cached_relationships_hash(source, fetchable_fields, relationship_data)
|
178
|
+
obj_hash['relationships'] = relationships unless relationships.blank?
|
156
179
|
|
157
180
|
obj_hash['meta'] = source.meta_json if source.meta_json
|
158
181
|
else
|
159
|
-
fetchable_fields = Set.new(source.fetchable_fields)
|
160
|
-
|
161
182
|
# TODO Should this maybe be using @id_formatter instead, for consistency?
|
162
183
|
id_format = source.class._attribute_options(:id)[:format]
|
163
184
|
# protect against ids that were declared as an attribute, but did not have a format set.
|
@@ -172,8 +193,8 @@ module JSONAPI
|
|
172
193
|
attributes = attributes_hash(source, fetchable_fields)
|
173
194
|
obj_hash['attributes'] = attributes unless attributes.empty?
|
174
195
|
|
175
|
-
relationships = relationships_hash(source, fetchable_fields,
|
176
|
-
obj_hash['relationships'] = relationships unless relationships.
|
196
|
+
relationships = relationships_hash(source, fetchable_fields, relationship_data)
|
197
|
+
obj_hash['relationships'] = relationships unless relationships.blank?
|
177
198
|
|
178
199
|
meta = meta_hash(source)
|
179
200
|
obj_hash['meta'] = meta unless meta.empty?
|
@@ -184,24 +205,11 @@ module JSONAPI
|
|
184
205
|
|
185
206
|
private
|
186
207
|
|
187
|
-
# Process the primary source object(s). This will then serialize associated object recursively based on the
|
188
|
-
# requested includes. Fields are controlled fields option for each resource type, such
|
189
|
-
# as fields: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
|
190
|
-
# The fields options controls both fields and included links references.
|
191
|
-
def process_source_objects(source, include_directives)
|
192
|
-
if source.respond_to?(:to_ary)
|
193
|
-
source.each { |resource| process_source_objects(resource, include_directives) }
|
194
|
-
else
|
195
|
-
return {} if source.nil?
|
196
|
-
add_resource(source, include_directives, true)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
208
|
def supplying_attribute_fields(resource_klass)
|
201
209
|
@_supplying_attribute_fields.fetch resource_klass do
|
202
210
|
attrs = Set.new(resource_klass._attributes.keys.map(&:to_sym))
|
203
211
|
cur = resource_klass
|
204
|
-
while cur
|
212
|
+
while !cur.root? # do not traverse beyond the first root resource
|
205
213
|
if @fields.has_key?(cur._type)
|
206
214
|
attrs &= @fields[cur._type]
|
207
215
|
break
|
@@ -216,7 +224,7 @@ module JSONAPI
|
|
216
224
|
@_supplying_relationship_fields.fetch resource_klass do
|
217
225
|
relationships = Set.new(resource_klass._relationships.keys.map(&:to_sym))
|
218
226
|
cur = resource_klass
|
219
|
-
while cur
|
227
|
+
while !cur.root? # do not traverse beyond the first root resource
|
220
228
|
if @fields.has_key?(cur._type)
|
221
229
|
relationships &= @fields[cur._type]
|
222
230
|
break
|
@@ -238,7 +246,7 @@ module JSONAPI
|
|
238
246
|
end
|
239
247
|
|
240
248
|
def custom_generation_options
|
241
|
-
{
|
249
|
+
@_custom_generation_options ||= {
|
242
250
|
serializer: self,
|
243
251
|
serialization_options: @serialization_options
|
244
252
|
}
|
@@ -251,7 +259,9 @@ module JSONAPI
|
|
251
259
|
|
252
260
|
def links_hash(source)
|
253
261
|
links = custom_links_hash(source)
|
254
|
-
links
|
262
|
+
if !links.key?('self') && !source.class.exclude_link?(:self)
|
263
|
+
links['self'] = link_builder.self_link(source)
|
264
|
+
end
|
255
265
|
links.compact
|
256
266
|
end
|
257
267
|
|
@@ -260,116 +270,62 @@ module JSONAPI
|
|
260
270
|
(custom_links.is_a?(Hash) && custom_links) || {}
|
261
271
|
end
|
262
272
|
|
263
|
-
def
|
264
|
-
|
265
|
-
when CachedResourceFragment then "#{source.resource_klass}_#{source.id}"
|
266
|
-
when Resource then "#{source.class}_#{@id_formatter.format(source.id)}"
|
267
|
-
else raise "Unknown source type #{source.inspect}"
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def self_referential_and_already_in_source(resource)
|
272
|
-
resource && @top_level_sources.include?(top_level_source_key(resource))
|
273
|
-
end
|
274
|
-
|
275
|
-
def relationships_hash(source, fetchable_fields, include_directives = {})
|
276
|
-
if source.is_a?(CachedResourceFragment)
|
277
|
-
return cached_relationships_hash(source, include_directives)
|
278
|
-
end
|
279
|
-
|
280
|
-
include_directives[:include_related] ||= {}
|
281
|
-
|
282
|
-
relationships = source.class._relationships.select{|k,v| fetchable_fields.include?(k) }
|
273
|
+
def relationships_hash(source, fetchable_fields, relationship_data)
|
274
|
+
relationships = source.class._relationships.select{|k,_v| fetchable_fields.include?(k) }
|
283
275
|
field_set = supplying_relationship_fields(source.class) & relationships.keys
|
284
276
|
|
285
277
|
relationships.each_with_object({}) do |(name, relationship), hash|
|
286
|
-
|
287
|
-
include_linkage = ia && ia[:include]
|
288
|
-
include_linked_children = ia && !ia[:include_related].empty?
|
289
|
-
|
278
|
+
include_data = false
|
290
279
|
if field_set.include?(name)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
if include_linkage || include_linked_children
|
298
|
-
resources = if source.preloaded_fragments.has_key?(format_key(name))
|
299
|
-
source.preloaded_fragments[format_key(name)].values
|
300
|
-
else
|
301
|
-
[source.public_send(name)].flatten(1).compact
|
302
|
-
end
|
303
|
-
resources.each do |resource|
|
304
|
-
next if self_referential_and_already_in_source(resource)
|
305
|
-
id = resource.id
|
306
|
-
relationships_only = already_serialized?(relationship.type, id)
|
307
|
-
if include_linkage && !relationships_only
|
308
|
-
add_resource(resource, ia)
|
309
|
-
elsif include_linked_children || relationships_only
|
310
|
-
relationships_hash(resource, fetchable_fields, ia)
|
280
|
+
if relationship_data[name]
|
281
|
+
include_data = true
|
282
|
+
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
283
|
+
rids = relationship_data[name].first
|
284
|
+
else
|
285
|
+
rids = relationship_data[name]
|
311
286
|
end
|
312
287
|
end
|
288
|
+
|
289
|
+
ro = relationship_object(source, relationship, rids, include_data)
|
290
|
+
hash[format_key(name)] = ro unless ro.blank?
|
313
291
|
end
|
314
292
|
end
|
315
293
|
end
|
316
294
|
|
317
|
-
def cached_relationships_hash(source,
|
318
|
-
|
319
|
-
return h unless include_directives.has_key?(:include_related)
|
295
|
+
def cached_relationships_hash(source, fetchable_fields, relationship_data)
|
296
|
+
relationships = {}
|
320
297
|
|
321
|
-
|
322
|
-
|
298
|
+
source.relationships.try(:each_pair) do |k,v|
|
299
|
+
if fetchable_fields.include?(unformat_key(k).to_sym)
|
300
|
+
relationships[k.to_sym] = v
|
301
|
+
end
|
323
302
|
end
|
324
303
|
|
325
|
-
|
326
|
-
relationships.each do |rel_name, relationship|
|
327
|
-
key = @key_formatter.format(rel_name)
|
328
|
-
to_many = relationship.is_a? JSONAPI::Relationship::ToMany
|
304
|
+
field_set = supplying_relationship_fields(source.resource_klass).collect {|k| format_key(k).to_sym } & relationships.keys
|
329
305
|
|
330
|
-
|
331
|
-
if
|
332
|
-
if h.has_key?(key)
|
333
|
-
h[key][:data] = to_many ? [] : nil
|
334
|
-
end
|
306
|
+
relationships.each_with_object({}) do |(name, relationship), hash|
|
307
|
+
if field_set.include?(name)
|
335
308
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
309
|
+
relationship_name = unformat_key(name).to_sym
|
310
|
+
relationship_klass = source.resource_klass._relationships[relationship_name]
|
311
|
+
|
312
|
+
if relationship_klass.is_a?(JSONAPI::Relationship::ToOne)
|
313
|
+
# include_linkage = @always_include_to_one_linkage_data | relationship_klass.always_include_linkage_data
|
314
|
+
if relationship_data[relationship_name]
|
315
|
+
rids = relationship_data[relationship_name].first
|
316
|
+
relationship['data'] = to_one_linkage(rids)
|
342
317
|
end
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
if h.has_key?(key)
|
350
|
-
# The hash already has everything we need except the :data field
|
351
|
-
data = {
|
352
|
-
type: format_key(f.is_a?(Resource) ? f.class._type : f.type),
|
353
|
-
id: @id_formatter.format(id)
|
354
|
-
}
|
355
|
-
|
356
|
-
if to_many
|
357
|
-
h[key][:data] << data
|
358
|
-
else
|
359
|
-
h[key][:data] = data
|
360
|
-
end
|
318
|
+
else
|
319
|
+
# include_linkage = relationship_klass.always_include_linkage_data
|
320
|
+
if relationship_data[relationship_name]
|
321
|
+
rids = relationship_data[relationship_name]
|
322
|
+
relationship['data'] = to_many_linkage(rids)
|
361
323
|
end
|
362
324
|
end
|
325
|
+
|
326
|
+
hash[format_key(name)] = relationship
|
363
327
|
end
|
364
328
|
end
|
365
|
-
|
366
|
-
return h
|
367
|
-
end
|
368
|
-
|
369
|
-
def already_serialized?(type, id)
|
370
|
-
type = format_key(type)
|
371
|
-
id = @id_formatter.format(id)
|
372
|
-
@included_objects.key?(type) && @included_objects[type].key?(id)
|
373
329
|
end
|
374
330
|
|
375
331
|
def self_link(source, relationship)
|
@@ -380,151 +336,69 @@ module JSONAPI
|
|
380
336
|
link_builder.relationships_related_link(source, relationship)
|
381
337
|
end
|
382
338
|
|
383
|
-
def
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
{
|
389
|
-
type: linkage_type,
|
390
|
-
id: linkage_id,
|
391
|
-
}
|
339
|
+
def default_relationship_links(source, relationship)
|
340
|
+
links = {}
|
341
|
+
links['self'] = self_link(source, relationship) unless relationship.exclude_link?(:self)
|
342
|
+
links['related'] = related_link(source, relationship) unless relationship.exclude_link?(:related)
|
343
|
+
links.compact
|
392
344
|
end
|
393
345
|
|
394
|
-
def to_many_linkage(
|
346
|
+
def to_many_linkage(rids)
|
395
347
|
linkage = []
|
396
|
-
linkage_types_and_values = if source.preloaded_fragments.has_key?(format_key(relationship.name))
|
397
|
-
source.preloaded_fragments[format_key(relationship.name)].map do |_, resource|
|
398
|
-
[relationship.type, resource.id]
|
399
|
-
end
|
400
|
-
elsif relationship.polymorphic?
|
401
|
-
assoc = source._model.public_send(relationship.name)
|
402
|
-
# Avoid hitting the database again for values already pre-loaded
|
403
|
-
if assoc.respond_to?(:loaded?) and assoc.loaded?
|
404
|
-
assoc.map do |obj|
|
405
|
-
[obj.type.underscore.pluralize, obj.id]
|
406
|
-
end
|
407
|
-
else
|
408
|
-
assoc.pluck(:type, :id).map do |type, id|
|
409
|
-
[type.underscore.pluralize, id]
|
410
|
-
end
|
411
|
-
end
|
412
|
-
else
|
413
|
-
source.public_send(relationship.name).map do |value|
|
414
|
-
[relationship.type, value.id]
|
415
|
-
end
|
416
|
-
end
|
417
348
|
|
418
|
-
|
419
|
-
|
420
|
-
|
349
|
+
rids && rids.each do |details|
|
350
|
+
id = details.id
|
351
|
+
type = details.resource_klass.try(:_type)
|
352
|
+
if type && id
|
353
|
+
linkage.append({'type' => format_key(type), 'id' => @id_formatter.format(id)})
|
421
354
|
end
|
422
355
|
end
|
356
|
+
|
423
357
|
linkage
|
424
358
|
end
|
425
359
|
|
426
|
-
def
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
link_object_hash
|
360
|
+
def to_one_linkage(rid)
|
361
|
+
return unless rid
|
362
|
+
|
363
|
+
{
|
364
|
+
'type' => format_key(rid.resource_klass._type),
|
365
|
+
'id' => @id_formatter.format(rid.id),
|
366
|
+
}
|
434
367
|
end
|
435
368
|
|
436
|
-
def
|
437
|
-
include_linkage = include_linkage | relationship.always_include_linkage_data
|
369
|
+
def relationship_object_to_one(source, relationship, rid, include_data)
|
438
370
|
link_object_hash = {}
|
439
|
-
link_object_hash[:links] = {}
|
440
|
-
link_object_hash[:links][:self] = self_link(source, relationship)
|
441
|
-
link_object_hash[:links][:related] = related_link(source, relationship)
|
442
|
-
link_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
|
443
|
-
link_object_hash
|
444
|
-
end
|
445
371
|
|
446
|
-
|
447
|
-
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
448
|
-
link_object_to_one(source, relationship, include_linkage)
|
449
|
-
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
450
|
-
link_object_to_many(source, relationship, include_linkage)
|
451
|
-
end
|
452
|
-
end
|
372
|
+
links = default_relationship_links(source, relationship)
|
453
373
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
source.preloaded_fragments[format_key(relationship.name)].values.first.try(:id)
|
458
|
-
elsif source.respond_to?("#{relationship.name}_id")
|
459
|
-
# If you have direct access to the underlying id, you don't have to load the relationship
|
460
|
-
# which can save quite a lot of time when loading a lot of data.
|
461
|
-
# This does not apply to e.g. has_one :through relationships.
|
462
|
-
source.public_send("#{relationship.name}_id")
|
463
|
-
else
|
464
|
-
source.public_send(relationship.name).try(:id)
|
465
|
-
end
|
466
|
-
return nil unless related_resource_id
|
467
|
-
@id_formatter.format(related_resource_id)
|
374
|
+
link_object_hash['links'] = links unless links.blank?
|
375
|
+
link_object_hash['data'] = to_one_linkage(rid) if include_data
|
376
|
+
link_object_hash
|
468
377
|
end
|
469
378
|
|
470
|
-
def
|
471
|
-
|
472
|
-
if relationship.polymorphic?
|
473
|
-
assoc = source._model.public_send(relationship.name)
|
474
|
-
# Avoid hitting the database again for values already pre-loaded
|
475
|
-
if assoc.respond_to?(:loaded?) and assoc.loaded?
|
476
|
-
assoc.map do |obj|
|
477
|
-
[obj.type.underscore.pluralize, @id_formatter.format(obj.id)]
|
478
|
-
end
|
479
|
-
else
|
480
|
-
assoc.pluck(:type, :id).map do |type, id|
|
481
|
-
[type.underscore.pluralize, @id_formatter.format(id)]
|
482
|
-
end
|
483
|
-
end
|
484
|
-
else
|
485
|
-
source.public_send(relationship.name).map do |value|
|
486
|
-
[relationship.type, @id_formatter.format(value.id)]
|
487
|
-
end
|
488
|
-
end
|
489
|
-
end
|
490
|
-
end
|
379
|
+
def relationship_object_to_many(source, relationship, rids, include_data)
|
380
|
+
link_object_hash = {}
|
491
381
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
382
|
+
links = default_relationship_links(source, relationship)
|
383
|
+
link_object_hash['links'] = links unless links.blank?
|
384
|
+
link_object_hash['data'] = to_many_linkage(rids) if include_data
|
385
|
+
link_object_hash
|
496
386
|
end
|
497
387
|
|
498
|
-
def
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
existing = @included_objects[type][id]
|
504
|
-
|
505
|
-
if existing.nil?
|
506
|
-
obj_hash = object_hash(source, include_directives)
|
507
|
-
@included_objects[type][id] = {
|
508
|
-
primary: primary,
|
509
|
-
object_hash: obj_hash,
|
510
|
-
includes: Set.new(include_directives[:include_related].keys)
|
511
|
-
}
|
512
|
-
else
|
513
|
-
include_related = Set.new(include_directives[:include_related].keys)
|
514
|
-
unless existing[:includes].superset?(include_related)
|
515
|
-
obj_hash = object_hash(source, include_directives)
|
516
|
-
@included_objects[type][id][:object_hash].deep_merge!(obj_hash)
|
517
|
-
@included_objects[type][id][:includes].add(include_related)
|
518
|
-
@included_objects[type][id][:primary] = existing[:primary] | primary
|
519
|
-
end
|
388
|
+
def relationship_object(source, relationship, rid, include_data)
|
389
|
+
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
390
|
+
relationship_object_to_one(source, relationship, rid, include_data)
|
391
|
+
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
392
|
+
relationship_object_to_many(source, relationship, rid, include_data)
|
520
393
|
end
|
521
394
|
end
|
522
395
|
|
523
396
|
def generate_link_builder(primary_resource_klass, options)
|
524
397
|
LinkBuilder.new(
|
525
398
|
base_url: options.fetch(:base_url, ''),
|
526
|
-
route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
|
527
399
|
primary_resource_klass: primary_resource_klass,
|
400
|
+
route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
|
401
|
+
url_helpers: options.fetch(:url_helpers, options[:controller]),
|
528
402
|
)
|
529
403
|
end
|
530
404
|
end
|