jsonapi-resources 0.9.0 → 0.10.6

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