Skip to content

Commit 29a43c3

Browse files
committed
Allow related resources to be retrieved by inverse or primary on a per relationship basis
Add `find_related_through` to relationships with defaults controlled by the resource classes Move find related fragments through primary methods to shared module to allow sharing between the default and v10 strategies
1 parent 16ee576 commit 29a43c3

9 files changed

+511
-310
lines changed

lib/jsonapi-resources.rb

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'jsonapi/naive_cache'
66
require 'jsonapi/compiled_json'
77
require 'jsonapi/relation_retrieval'
8+
require 'jsonapi/active_relation_retrieval/find_related_through_primary'
89
require 'jsonapi/active_relation_retrieval'
910
require 'jsonapi/active_relation_retrieval_v09'
1011
require 'jsonapi/active_relation_retrieval_v10'

lib/jsonapi/active_relation_retrieval.rb

+73-29
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@
33
module JSONAPI
44
module ActiveRelationRetrieval
55
include ::JSONAPI::RelationRetrieval
6+
include ::JSONAPI::ActiveRelationRetrieval::FindRelatedThroughPrimary
67

78
def find_related_ids(relationship, options)
89
self.class.find_related_fragments(self, relationship, options).keys.collect { |rid| rid.id }
910
end
1011

1112
module ClassMethods
13+
include JSONAPI::ActiveRelationRetrieval::FindRelatedThroughPrimary::ClassMethods
14+
15+
def default_find_related_through(polymorphic = false)
16+
if polymorphic
17+
JSONAPI.configuration.default_find_related_through_polymorphic
18+
else
19+
JSONAPI.configuration.default_find_related_through
20+
end
21+
end
22+
1223
# Finds Resources using the `filters`. Pagination and sort options are used when provided
1324
#
1425
# @param filters [Hash] the filters hash
@@ -119,6 +130,11 @@ def find_fragments(filters, options)
119130
options: options)
120131

121132
if options[:cache]
133+
# When using caching the a two step process is used. First the records ids are retrieved and then the
134+
# records are retrieved using the ids. Then the ids are used to query the database again to get the
135+
# cache misses. In the second phase the records are not sorted or paginated and the `records_for_populate`
136+
# method is used to ensure any dependent includes or custom database fields are calculated.
137+
122138
# This alias is going to be resolve down to the model's table name and will not actually be an alias
123139
resource_table_alias = resource_klass._table_name
124140

@@ -189,6 +205,10 @@ def find_fragments(filters, options)
189205
warn "Performance issue detected: `#{self.name.to_s}.records` returned non-normalized results in `#{self.name.to_s}.find_fragments`."
190206
end
191207
else
208+
# When not using caching resources can be generated after querying. The `records_for_populate`
209+
# method is merged in to ensure any dependent includes or custom database fields are calculated.
210+
records = records.merge(records_for_populate(options))
211+
192212
linkage_fields = []
193213

194214
linkage_relationships.each do |linkage_relationship|
@@ -260,50 +280,74 @@ def find_fragments(filters, options)
260280
# the ResourceInstances matching the filters, sorting, and pagination rules along with any request
261281
# additional_field values
262282
def find_related_fragments(source_fragment, relationship, options)
263-
if relationship.polymorphic? # && relationship.foreign_key_on == :self
264-
source_resource_klasses = if relationship.foreign_key_on == :self
265-
relationship.polymorphic_types.collect do |polymorphic_type|
266-
resource_klass_for(polymorphic_type)
283+
case relationship.find_related_through
284+
when :primary
285+
if relationship.polymorphic?
286+
find_related_polymorphic_fragments_through_primary([source_fragment], relationship, options, false)
287+
else
288+
find_related_monomorphic_fragments_through_primary([source_fragment], relationship, options, false)
289+
end
290+
when :inverse
291+
if relationship.polymorphic? # && relationship.foreign_key_on == :self
292+
source_resource_klasses = if relationship.foreign_key_on == :self
293+
relationship.polymorphic_types.collect do |polymorphic_type|
294+
resource_klass_for(polymorphic_type)
295+
end
296+
else
297+
source.collect { |fragment| fragment.identity.resource_klass }.to_set
267298
end
268-
else
269-
source.collect { |fragment| fragment.identity.resource_klass }.to_set
270-
end
271299

272-
fragments = {}
273-
source_resource_klasses.each do |resource_klass|
274-
inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize)
300+
fragments = {}
301+
source_resource_klasses.each do |resource_klass|
302+
inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize)
275303

276-
fragments.merge!(resource_klass.find_related_fragments_from_inverse([source_fragment], inverse_direct_relationship, options, false))
304+
fragments.merge!(resource_klass.find_related_fragments_through_inverse([source_fragment], inverse_direct_relationship, options, false))
305+
end
306+
fragments
307+
else
308+
relationship.resource_klass.find_related_fragments_through_inverse([source_fragment], relationship, options, false)
277309
end
278-
fragments
279310
else
280-
relationship.resource_klass.find_related_fragments_from_inverse([source_fragment], relationship, options, false)
311+
raise "Unknown find_related_through: #{relationship.find_related_through}"
312+
{}
281313
end
282314
end
283315

284316
def find_included_fragments(source_fragments, relationship, options)
285-
if relationship.polymorphic? # && relationship.foreign_key_on == :self
286-
source_resource_klasses = if relationship.foreign_key_on == :self
287-
relationship.polymorphic_types.collect do |polymorphic_type|
288-
resource_klass_for(polymorphic_type)
317+
case relationship.find_related_through
318+
when :primary
319+
if relationship.polymorphic?
320+
find_related_polymorphic_fragments_through_primary(source_fragments, relationship, options, true)
321+
else
322+
find_related_monomorphic_fragments_through_primary(source_fragments, relationship, options, true)
323+
end
324+
when :inverse
325+
if relationship.polymorphic? # && relationship.foreign_key_on == :self
326+
source_resource_klasses = if relationship.foreign_key_on == :self
327+
relationship.polymorphic_types.collect do |polymorphic_type|
328+
resource_klass_for(polymorphic_type)
329+
end
330+
else
331+
source.collect { |fragment| fragment.identity.resource_klass }.to_set
289332
end
290-
else
291-
source_fragments.collect { |fragment| fragment.identity.resource_klass }.to_set
292-
end
293333

294-
fragments = {}
295-
source_resource_klasses.each do |resource_klass|
296-
inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize)
334+
fragments = {}
335+
source_resource_klasses.each do |resource_klass|
336+
inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize)
297337

298-
fragments.merge!(resource_klass.find_related_fragments_from_inverse(source_fragments, inverse_direct_relationship, options, true))
338+
fragments.merge!(resource_klass.find_related_fragments_through_inverse(source_fragments, inverse_direct_relationship, options, true))
339+
end
340+
fragments
341+
else
342+
relationship.resource_klass.find_related_fragments_through_inverse(source_fragments, relationship, options, true)
299343
end
300-
fragments
301344
else
302-
relationship.resource_klass.find_related_fragments_from_inverse(source_fragments, relationship, options, true)
345+
raise "Unknown find_related_through: #{relationship.options[:find_related_through]}"
346+
{}
303347
end
304348
end
305349

306-
def find_related_fragments_from_inverse(source, source_relationship, options, connect_source_identity)
350+
def find_related_fragments_through_inverse(source, source_relationship, options, connect_source_identity)
307351
inverse_relationship = source_relationship._inverse_relationship
308352
return {} if inverse_relationship.blank?
309353

@@ -499,10 +543,10 @@ def find_related_fragments_from_inverse(source, source_relationship, options, co
499543
# @return [Integer] the count
500544

501545
def count_related(source, relationship, options)
502-
relationship.resource_klass.count_related_from_inverse(source, relationship, options)
546+
relationship.resource_klass.count_related_through_inverse(source, relationship, options)
503547
end
504548

505-
def count_related_from_inverse(source_resource, source_relationship, options)
549+
def count_related_through_inverse(source_resource, source_relationship, options)
506550
inverse_relationship = source_relationship._inverse_relationship
507551
return -1 if inverse_relationship.blank?
508552

0 commit comments

Comments
 (0)