jsonapi-resources 0.9.0 → 0.10.6

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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +303 -0
  8. data/lib/jsonapi/active_relation_resource.rb +884 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +71 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +80 -50
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -65
  19. data/lib/jsonapi/link_builder.rb +74 -80
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +74 -16
  22. data/lib/jsonapi/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +239 -111
  25. data/lib/jsonapi/relationship.rb +153 -15
  26. data/lib/jsonapi/request_parser.rb +430 -367
  27. data/lib/jsonapi/resource.rb +3 -1253
  28. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  29. data/lib/jsonapi/resource_fragment.rb +47 -0
  30. data/lib/jsonapi/resource_id_tree.rb +112 -0
  31. data/lib/jsonapi/resource_identity.rb +42 -0
  32. data/lib/jsonapi/resource_serializer.rb +143 -285
  33. data/lib/jsonapi/resource_set.rb +176 -0
  34. data/lib/jsonapi/resources/railtie.rb +9 -0
  35. data/lib/jsonapi/resources/version.rb +1 -1
  36. data/lib/jsonapi/response_document.rb +105 -83
  37. data/lib/jsonapi/routing_ext.rb +48 -26
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +50 -20
  41. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  42. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  43. data/lib/jsonapi/operation_results.rb +0 -35
  44. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -1,7 +1,7 @@
1
1
  module JSONAPI
2
2
  class ResourceSerializer
3
3
 
4
- attr_reader :link_builder, :key_formatter, :serialization_options, :primary_class_name,
4
+ attr_reader :link_builder, :key_formatter, :serialization_options,
5
5
  :fields, :include_directives, :always_include_to_one_linkage_data,
6
6
  :always_include_to_many_linkage_data
7
7
 
@@ -19,7 +19,6 @@ module JSONAPI
19
19
 
20
20
  def initialize(primary_resource_klass, options = {})
21
21
  @primary_resource_klass = primary_resource_klass
22
- @primary_class_name = primary_resource_klass._type
23
22
  @fields = options.fetch(:fields, {})
24
23
  @include = options.fetch(:include, [])
25
24
  @include_directives = options[:include_directives]
@@ -42,78 +41,81 @@ module JSONAPI
42
41
  @_supplying_relationship_fields = {}
43
42
  end
44
43
 
45
- # Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
46
- def serialize_to_hash(source)
47
- @top_level_sources = Set.new([source].flatten(1).compact.map {|s| top_level_source_key(s) })
48
-
49
- is_resource_collection = source.respond_to?(:to_ary)
50
-
51
- @included_objects = {}
52
-
53
- process_source_objects(source, @include_directives.include_directives)
44
+ # Converts a resource_set to a hash, conforming to the JSONAPI structure
45
+ def serialize_resource_set_to_hash_single(resource_set)
54
46
 
55
47
  primary_objects = []
48
+ included_objects = []
56
49
 
57
- # pull the processed objects corresponding to the source objects. Ensures we preserve order.
58
- if is_resource_collection
59
- source.each do |primary|
60
- if primary.id
61
- case primary
62
- when CachedResourceFragment then primary_objects.push(@included_objects[primary.type][primary.id][:object_hash])
63
- when Resource then primary_objects.push(@included_objects[primary.class._type][primary.id][:object_hash])
64
- else raise "Unknown source type #{primary.inspect}"
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}"
50
+ resource_set.resource_klasses.each_value do |resource_klass|
51
+ resource_klass.each_value do |resource|
52
+ serialized_resource = object_hash(resource[:resource], resource[:relationships])
53
+
54
+ if resource[:primary]
55
+ primary_objects.push(serialized_resource)
56
+ else
57
+ included_objects.push(serialized_resource)
74
58
  end
75
59
  end
76
60
  end
77
61
 
62
+ fail "Too many primary objects for show" if (primary_objects.count > 1)
63
+ primary_hash = { 'data' => primary_objects[0] }
64
+
65
+ primary_hash['included'] = included_objects if included_objects.size > 0
66
+ primary_hash
67
+ end
68
+
69
+ def serialize_resource_set_to_hash_plural(resource_set)
70
+
71
+ primary_objects = []
78
72
  included_objects = []
79
- @included_objects.each_value do |objects|
80
- objects.each_value do |object|
81
- unless object[:primary]
82
- included_objects.push(object[:object_hash])
73
+
74
+ resource_set.resource_klasses.each_value do |resource_klass|
75
+ resource_klass.each_value do |resource|
76
+ serialized_resource = object_hash(resource[:resource], resource[:relationships])
77
+
78
+ if resource[:primary]
79
+ primary_objects.push(serialized_resource)
80
+ else
81
+ included_objects.push(serialized_resource)
83
82
  end
84
83
  end
85
84
  end
86
85
 
87
- primary_hash = { data: is_resource_collection ? primary_objects : primary_objects[0] }
86
+ primary_hash = { 'data' => primary_objects }
88
87
 
89
- primary_hash[:included] = included_objects if included_objects.size > 0
88
+ primary_hash['included'] = included_objects if included_objects.size > 0
90
89
  primary_hash
91
90
  end
92
91
 
93
- def serialize_to_links_hash(source, requested_relationship)
92
+ def serialize_related_resource_set_to_hash_plural(resource_set, _source_resource)
93
+ return serialize_resource_set_to_hash_plural(resource_set)
94
+ end
95
+
96
+ def serialize_to_relationship_hash(source, requested_relationship, resource_ids)
94
97
  if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
95
- data = to_one_linkage(source, requested_relationship)
98
+ data = to_one_linkage(resource_ids[0])
96
99
  else
97
- data = to_many_linkage(source, requested_relationship)
100
+ data = to_many_linkage(resource_ids)
98
101
  end
99
102
 
100
- {
101
- links: {
102
- self: self_link(source, requested_relationship),
103
- related: related_link(source, requested_relationship)
104
- },
105
- data: data
106
- }
107
- end
103
+ rel_hash = { 'data': data }
108
104
 
109
- def query_link(query_params)
110
- link_builder.query_link(query_params)
105
+ links = default_relationship_links(source, requested_relationship)
106
+ rel_hash['links'] = links unless links.blank?
107
+
108
+ rel_hash
111
109
  end
112
110
 
113
111
  def format_key(key)
114
112
  @key_formatter.format(key)
115
113
  end
116
114
 
115
+ def unformat_key(key)
116
+ @key_formatter.unformat(key)
117
+ end
118
+
117
119
  def format_value(value, format)
118
120
  @value_formatter_type_cache.get(format).format(value)
119
121
  end
@@ -129,35 +131,38 @@ module JSONAPI
129
131
  def config_description(resource_klass)
130
132
  {
131
133
  class_name: self.class.name,
132
- seriserialization_options: serialization_options.sort.map(&:as_json),
134
+ serialization_options: serialization_options.sort.map(&:as_json),
133
135
  supplying_attribute_fields: supplying_attribute_fields(resource_klass).sort,
134
136
  supplying_relationship_fields: supplying_relationship_fields(resource_klass).sort,
135
137
  link_builder_base_url: link_builder.base_url,
136
- route_formatter_class: link_builder.route_formatter.uncached.class.name,
137
138
  key_formatter_class: key_formatter.uncached.class.name,
138
139
  always_include_to_one_linkage_data: always_include_to_one_linkage_data,
139
140
  always_include_to_many_linkage_data: always_include_to_many_linkage_data
140
141
  }
141
142
  end
142
143
 
143
- # Returns a serialized hash for the source model
144
- def object_hash(source, include_directives = {})
144
+ def object_hash(source, relationship_data)
145
145
  obj_hash = {}
146
146
 
147
- if source.is_a?(JSONAPI::CachedResourceFragment)
148
- obj_hash['id'] = source.id
147
+ return obj_hash if source.nil?
148
+
149
+ fetchable_fields = Set.new(source.fetchable_fields)
150
+
151
+ if source.is_a?(JSONAPI::CachedResponseFragment)
152
+ id_format = source.resource_klass._attribute_options(:id)[:format]
153
+
154
+ id_format = 'id' if id_format == :default
155
+ obj_hash['id'] = format_value(source.id, id_format)
149
156
  obj_hash['type'] = source.type
150
157
 
151
158
  obj_hash['links'] = source.links_json if source.links_json
152
159
  obj_hash['attributes'] = source.attributes_json if source.attributes_json
153
160
 
154
- relationships = cached_relationships_hash(source, include_directives)
155
- obj_hash['relationships'] = relationships unless relationships.empty?
161
+ relationships = cached_relationships_hash(source, fetchable_fields, relationship_data)
162
+ obj_hash['relationships'] = relationships unless relationships.blank?
156
163
 
157
164
  obj_hash['meta'] = source.meta_json if source.meta_json
158
165
  else
159
- fetchable_fields = Set.new(source.fetchable_fields)
160
-
161
166
  # TODO Should this maybe be using @id_formatter instead, for consistency?
162
167
  id_format = source.class._attribute_options(:id)[:format]
163
168
  # protect against ids that were declared as an attribute, but did not have a format set.
@@ -172,8 +177,8 @@ module JSONAPI
172
177
  attributes = attributes_hash(source, fetchable_fields)
173
178
  obj_hash['attributes'] = attributes unless attributes.empty?
174
179
 
175
- relationships = relationships_hash(source, fetchable_fields, include_directives)
176
- obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
180
+ relationships = relationships_hash(source, fetchable_fields, relationship_data)
181
+ obj_hash['relationships'] = relationships unless relationships.blank?
177
182
 
178
183
  meta = meta_hash(source)
179
184
  obj_hash['meta'] = meta unless meta.empty?
@@ -184,24 +189,11 @@ module JSONAPI
184
189
 
185
190
  private
186
191
 
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
192
  def supplying_attribute_fields(resource_klass)
201
193
  @_supplying_attribute_fields.fetch resource_klass do
202
194
  attrs = Set.new(resource_klass._attributes.keys.map(&:to_sym))
203
195
  cur = resource_klass
204
- while cur != JSONAPI::Resource
196
+ while !cur.root? # do not traverse beyond the first root resource
205
197
  if @fields.has_key?(cur._type)
206
198
  attrs &= @fields[cur._type]
207
199
  break
@@ -216,7 +208,7 @@ module JSONAPI
216
208
  @_supplying_relationship_fields.fetch resource_klass do
217
209
  relationships = Set.new(resource_klass._relationships.keys.map(&:to_sym))
218
210
  cur = resource_klass
219
- while cur != JSONAPI::Resource
211
+ while !cur.root? # do not traverse beyond the first root resource
220
212
  if @fields.has_key?(cur._type)
221
213
  relationships &= @fields[cur._type]
222
214
  break
@@ -238,7 +230,7 @@ module JSONAPI
238
230
  end
239
231
 
240
232
  def custom_generation_options
241
- {
233
+ @_custom_generation_options ||= {
242
234
  serializer: self,
243
235
  serialization_options: @serialization_options
244
236
  }
@@ -251,7 +243,9 @@ module JSONAPI
251
243
 
252
244
  def links_hash(source)
253
245
  links = custom_links_hash(source)
254
- links[:self] = link_builder.self_link(source) unless links.key?(:self)
246
+ if !links.key?('self') && !source.class.exclude_link?(:self)
247
+ links['self'] = link_builder.self_link(source)
248
+ end
255
249
  links.compact
256
250
  end
257
251
 
@@ -260,116 +254,62 @@ module JSONAPI
260
254
  (custom_links.is_a?(Hash) && custom_links) || {}
261
255
  end
262
256
 
263
- def top_level_source_key(source)
264
- case source
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) }
257
+ def relationships_hash(source, fetchable_fields, relationship_data)
258
+ relationships = source.class._relationships.select{|k,_v| fetchable_fields.include?(k) }
283
259
  field_set = supplying_relationship_fields(source.class) & relationships.keys
284
260
 
285
261
  relationships.each_with_object({}) do |(name, relationship), hash|
286
- ia = include_directives[:include_related][name]
287
- include_linkage = ia && ia[:include]
288
- include_linked_children = ia && !ia[:include_related].empty?
289
-
262
+ include_data = false
290
263
  if field_set.include?(name)
291
- hash[format_key(name)] = link_object(source, relationship, include_linkage)
292
- end
293
-
294
- # If the object has been serialized once it will be in the related objects list,
295
- # but it's possible all children won't have been captured. So we must still go
296
- # through the relationships.
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)
264
+ if relationship_data[name]
265
+ include_data = true
266
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
267
+ rids = relationship_data[name].first
268
+ else
269
+ rids = relationship_data[name]
311
270
  end
312
271
  end
272
+
273
+ ro = relationship_object(source, relationship, rids, include_data)
274
+ hash[format_key(name)] = ro unless ro.blank?
313
275
  end
314
276
  end
315
277
  end
316
278
 
317
- def cached_relationships_hash(source, include_directives)
318
- h = source.relationships || {}
319
- return h unless include_directives.has_key?(:include_related)
279
+ def cached_relationships_hash(source, fetchable_fields, relationship_data)
280
+ relationships = {}
320
281
 
321
- relationships = source.resource_klass._relationships.select do |k,v|
322
- source.fetchable_fields.include?(k)
282
+ source.relationships.try(:each_pair) do |k,v|
283
+ if fetchable_fields.include?(unformat_key(k).to_sym)
284
+ relationships[k.to_sym] = v
285
+ end
323
286
  end
324
287
 
325
- real_res = nil
326
- relationships.each do |rel_name, relationship|
327
- key = @key_formatter.format(rel_name)
328
- to_many = relationship.is_a? JSONAPI::Relationship::ToMany
288
+ field_set = supplying_relationship_fields(source.resource_klass).collect {|k| format_key(k).to_sym } & relationships.keys
329
289
 
330
- ia = include_directives[:include_related][rel_name]
331
- if ia
332
- if h.has_key?(key)
333
- h[key][:data] = to_many ? [] : nil
334
- end
290
+ relationships.each_with_object({}) do |(name, relationship), hash|
291
+ if field_set.include?(name)
292
+
293
+ relationship_name = unformat_key(name).to_sym
294
+ relationship_klass = source.resource_klass._relationships[relationship_name]
335
295
 
336
- fragments = source.preloaded_fragments[key]
337
- if fragments.nil?
338
- # The resources we want were not preloaded, we'll have to bypass the cache.
339
- # This happens when including through belongs_to polymorphic relationships
340
- if real_res.nil?
341
- real_res = source.to_real_resource
296
+ if relationship_klass.is_a?(JSONAPI::Relationship::ToOne)
297
+ # include_linkage = @always_include_to_one_linkage_data | relationship_klass.always_include_linkage_data
298
+ if relationship_data[relationship_name]
299
+ rids = relationship_data[relationship_name].first
300
+ relationship['data'] = to_one_linkage(rids)
342
301
  end
343
- relation_resources = [real_res.public_send(rel_name)].flatten(1).compact
344
- fragments = relation_resources.map{|r| [r.id, r]}.to_h
345
- end
346
- fragments.each do |id, f|
347
- add_resource(f, ia)
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
302
+ else
303
+ # include_linkage = relationship_klass.always_include_linkage_data
304
+ if relationship_data[relationship_name]
305
+ rids = relationship_data[relationship_name]
306
+ relationship['data'] = to_many_linkage(rids)
361
307
  end
362
308
  end
309
+
310
+ hash[format_key(name)] = relationship
363
311
  end
364
312
  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
313
  end
374
314
 
375
315
  def self_link(source, relationship)
@@ -380,151 +320,69 @@ module JSONAPI
380
320
  link_builder.relationships_related_link(source, relationship)
381
321
  end
382
322
 
383
- def to_one_linkage(source, relationship)
384
- linkage_id = foreign_key_value(source, relationship)
385
- linkage_type = format_key(relationship.type_for_source(source))
386
- return unless linkage_id.present? && linkage_type.present?
387
-
388
- {
389
- type: linkage_type,
390
- id: linkage_id,
391
- }
323
+ def default_relationship_links(source, relationship)
324
+ links = {}
325
+ links['self'] = self_link(source, relationship) unless relationship.exclude_link?(:self)
326
+ links['related'] = related_link(source, relationship) unless relationship.exclude_link?(:related)
327
+ links.compact
392
328
  end
393
329
 
394
- def to_many_linkage(source, relationship)
330
+ def to_many_linkage(rids)
395
331
  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
332
 
418
- linkage_types_and_values.each do |type, value|
419
- if type && value
420
- linkage.append({type: format_key(type), id: @id_formatter.format(value)})
333
+ rids && rids.each do |details|
334
+ id = details.id
335
+ type = details.resource_klass.try(:_type)
336
+ if type && id
337
+ linkage.append({'type' => format_key(type), 'id' => @id_formatter.format(id)})
421
338
  end
422
339
  end
340
+
423
341
  linkage
424
342
  end
425
343
 
426
- def link_object_to_one(source, relationship, include_linkage)
427
- include_linkage = include_linkage | @always_include_to_one_linkage_data | relationship.always_include_linkage_data
428
- link_object_hash = {}
429
- link_object_hash[:links] = {}
430
- link_object_hash[:links][:self] = self_link(source, relationship)
431
- link_object_hash[:links][:related] = related_link(source, relationship)
432
- link_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
433
- link_object_hash
344
+ def to_one_linkage(rid)
345
+ return unless rid
346
+
347
+ {
348
+ 'type' => format_key(rid.resource_klass._type),
349
+ 'id' => @id_formatter.format(rid.id),
350
+ }
434
351
  end
435
352
 
436
- def link_object_to_many(source, relationship, include_linkage)
437
- include_linkage = include_linkage | relationship.always_include_linkage_data
353
+ def relationship_object_to_one(source, relationship, rid, include_data)
438
354
  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
355
 
446
- def link_object(source, relationship, include_linkage = false)
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
356
+ links = default_relationship_links(source, relationship)
453
357
 
454
- # Extracts the foreign key value for a to_one relationship.
455
- def foreign_key_value(source, relationship)
456
- related_resource_id = if source.preloaded_fragments.has_key?(format_key(relationship.name))
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)
358
+ link_object_hash['links'] = links unless links.blank?
359
+ link_object_hash['data'] = to_one_linkage(rid) if include_data
360
+ link_object_hash
468
361
  end
469
362
 
470
- def foreign_key_types_and_values(source, relationship)
471
- if relationship.is_a?(JSONAPI::Relationship::ToMany)
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
363
+ def relationship_object_to_many(source, relationship, rids, include_data)
364
+ link_object_hash = {}
491
365
 
492
- # Sets that an object should be included in the primary document of the response.
493
- def set_primary(type, id)
494
- type = format_key(type)
495
- @included_objects[type][id][:primary] = true
366
+ links = default_relationship_links(source, relationship)
367
+ link_object_hash['links'] = links unless links.blank?
368
+ link_object_hash['data'] = to_many_linkage(rids) if include_data
369
+ link_object_hash
496
370
  end
497
371
 
498
- def add_resource(source, include_directives, primary = false)
499
- type = source.is_a?(JSONAPI::CachedResourceFragment) ? source.type : source.class._type
500
- id = source.id
501
-
502
- @included_objects[type] ||= {}
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
372
+ def relationship_object(source, relationship, rid, include_data)
373
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
374
+ relationship_object_to_one(source, relationship, rid, include_data)
375
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
376
+ relationship_object_to_many(source, relationship, rid, include_data)
520
377
  end
521
378
  end
522
379
 
523
380
  def generate_link_builder(primary_resource_klass, options)
524
381
  LinkBuilder.new(
525
382
  base_url: options.fetch(:base_url, ''),
526
- route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
527
383
  primary_resource_klass: primary_resource_klass,
384
+ route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
385
+ url_helpers: options.fetch(:url_helpers, options[:controller]),
528
386
  )
529
387
  end
530
388
  end