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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +35 -12
  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 +26 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +898 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +130 -113
  10. data/lib/jsonapi/basic_resource.rb +1164 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +129 -0
  12. data/lib/jsonapi/callbacks.rb +2 -0
  13. data/lib/jsonapi/compatibility_helper.rb +29 -0
  14. data/lib/jsonapi/compiled_json.rb +13 -1
  15. data/lib/jsonapi/configuration.rb +88 -21
  16. data/lib/jsonapi/error.rb +29 -0
  17. data/lib/jsonapi/error_codes.rb +4 -0
  18. data/lib/jsonapi/exceptions.rb +82 -50
  19. data/lib/jsonapi/formatter.rb +5 -3
  20. data/lib/jsonapi/include_directives.rb +22 -67
  21. data/lib/jsonapi/link_builder.rb +76 -80
  22. data/lib/jsonapi/mime_types.rb +6 -10
  23. data/lib/jsonapi/naive_cache.rb +2 -0
  24. data/lib/jsonapi/operation.rb +18 -5
  25. data/lib/jsonapi/operation_result.rb +76 -16
  26. data/lib/jsonapi/paginator.rb +2 -0
  27. data/lib/jsonapi/path.rb +45 -0
  28. data/lib/jsonapi/path_segment.rb +78 -0
  29. data/lib/jsonapi/processor.rb +193 -115
  30. data/lib/jsonapi/relationship.rb +145 -14
  31. data/lib/jsonapi/request.rb +734 -0
  32. data/lib/jsonapi/resource.rb +3 -1251
  33. data/lib/jsonapi/resource_controller.rb +2 -0
  34. data/lib/jsonapi/resource_controller_metal.rb +7 -1
  35. data/lib/jsonapi/resource_fragment.rb +56 -0
  36. data/lib/jsonapi/resource_identity.rb +44 -0
  37. data/lib/jsonapi/resource_serializer.rb +158 -284
  38. data/lib/jsonapi/resource_set.rb +196 -0
  39. data/lib/jsonapi/resource_tree.rb +236 -0
  40. data/lib/jsonapi/resources/railtie.rb +9 -0
  41. data/lib/jsonapi/resources/version.rb +1 -1
  42. data/lib/jsonapi/response_document.rb +107 -83
  43. data/lib/jsonapi/routing_ext.rb +50 -26
  44. data/lib/jsonapi-resources.rb +23 -5
  45. data/lib/tasks/check_upgrade.rake +52 -0
  46. metadata +43 -31
  47. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  48. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  49. data/lib/jsonapi/operation_results.rb +0 -35
  50. data/lib/jsonapi/relationship_builder.rb +0 -167
  51. 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, :primary_class_name,
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[:include_directives]
26
- @include_directives ||= JSONAPI::IncludeDirectives.new(@primary_resource_klass, @include)
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
- @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)
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
- @included_objects = {}
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
- process_source_objects(source, @include_directives.include_directives)
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
- # 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}"
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
- @included_objects.each_value do |objects|
80
- objects.each_value do |object|
81
- unless object[:primary]
82
- included_objects.push(object[:object_hash])
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: is_resource_collection ? primary_objects : primary_objects[0] }
102
+ primary_hash = { 'data' => primary_objects }
88
103
 
89
- primary_hash[:included] = included_objects if included_objects.size > 0
104
+ primary_hash['included'] = included_objects if included_objects.size > 0
90
105
  primary_hash
91
106
  end
92
107
 
93
- def serialize_to_links_hash(source, requested_relationship)
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(source, requested_relationship)
114
+ data = to_one_linkage(resource_ids[0])
96
115
  else
97
- data = to_many_linkage(source, requested_relationship)
116
+ data = to_many_linkage(resource_ids)
98
117
  end
99
118
 
100
- {
101
- links: {
102
- self: self_link(source, requested_relationship),
103
- related: related_link(source, requested_relationship)
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
- def query_link(query_params)
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
- seriserialization_options: serialization_options.sort.map(&:as_json),
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
- # Returns a serialized hash for the source model
144
- def object_hash(source, include_directives = {})
160
+ def object_hash(source, relationship_data)
145
161
  obj_hash = {}
146
162
 
147
- if source.is_a?(JSONAPI::CachedResourceFragment)
148
- obj_hash['id'] = source.id
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, include_directives)
155
- obj_hash['relationships'] = relationships unless relationships.empty?
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, include_directives)
176
- obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
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 != JSONAPI::Resource
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 != JSONAPI::Resource
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[:self] = link_builder.self_link(source) unless links.key?(:self)
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 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) }
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
- ia = include_directives[:include_related][name]
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
- 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)
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, include_directives)
318
- h = source.relationships || {}
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
- relationships = source.resource_klass._relationships.select do |k,v|
322
- source.fetchable_fields.include?(k)
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
- 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
304
+ field_set = supplying_relationship_fields(source.resource_klass).collect {|k| format_key(k).to_sym } & relationships.keys
329
305
 
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
306
+ relationships.each_with_object({}) do |(name, relationship), hash|
307
+ if field_set.include?(name)
335
308
 
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
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
- 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
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 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
- }
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(source, relationship)
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
- linkage_types_and_values.each do |type, value|
419
- if type && value
420
- linkage.append({type: format_key(type), id: @id_formatter.format(value)})
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 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
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 link_object_to_many(source, relationship, include_linkage)
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
- 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
372
+ links = default_relationship_links(source, relationship)
453
373
 
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)
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 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
379
+ def relationship_object_to_many(source, relationship, rids, include_data)
380
+ link_object_hash = {}
491
381
 
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
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 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
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