Skip to content

Commit 2abd000

Browse files
committed
Allow ActiveRecordRetrieval to support getting related resources through the inverse or primary resources
1 parent 557f348 commit 2abd000

12 files changed

+481
-157
lines changed

lib/jsonapi-resources.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
require 'jsonapi/callbacks'
4242
require 'jsonapi/link_builder'
4343
require 'jsonapi/active_relation/adapters/join_left_active_record_adapter'
44-
require 'jsonapi/active_relation/join_manager'
45-
require 'jsonapi/active_relation/join_manager_v10'
44+
require 'jsonapi/active_relation/join_manager_through_inverse'
45+
require 'jsonapi/active_relation/join_manager_through_primary'
4646
require 'jsonapi/resource_identity'
4747
require 'jsonapi/resource_fragment'
4848
require 'jsonapi/resource_tree'

lib/jsonapi/active_relation/join_manager.rb renamed to lib/jsonapi/active_relation/join_manager_through_inverse.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module ActiveRelation
55

66
# Stores relationship paths starting from the resource_klass, consolidating duplicate paths from
77
# relationships, filters and sorts. When joins are made the table aliases are tracked in join_details
8-
class JoinManager
8+
class JoinManagerThroughInverse
99
attr_reader :resource_klass,
1010
:source_relationship,
1111
:resource_join_tree,

lib/jsonapi/active_relation/join_manager_v10.rb renamed to lib/jsonapi/active_relation/join_manager_through_primary.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module ActiveRelation
55

66
# Stores relationship paths starting from the resource_klass, consolidating duplicate paths from
77
# relationships, filters and sorts. When joins are made the table aliases are tracked in join_details
8-
class JoinManagerV10
8+
class JoinManagerThroughPrimary
99
attr_reader :resource_klass,
1010
:source_relationship,
1111
:resource_join_tree,

lib/jsonapi/active_relation_retrieval.rb

Lines changed: 330 additions & 57 deletions
Large diffs are not rendered by default.

lib/jsonapi/active_relation_retrieval_v09.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def records_for(relation_name)
1515
end
1616

1717
module ClassMethods
18+
def default_find_related_through(polymorphic = false)
19+
polymorphic ? :model : :model
20+
end
21+
1822
# Finds Resources using the `filters`. Pagination and sort options are used when provided
1923
#
2024
# @param filters [Hash] the filters hash
@@ -103,9 +107,9 @@ def find_fragments(filters, options)
103107
sort_criteria = options.fetch(:sort_criteria) { [] }
104108
order_options = construct_order_options(sort_criteria)
105109

106-
join_manager = ActiveRelation::JoinManager.new(resource_klass: self,
107-
filters: filters,
108-
sort_criteria: sort_criteria)
110+
join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self,
111+
filters: filters,
112+
sort_criteria: sort_criteria)
109113

110114
options[:_relation_helper_options] = {
111115
context: context,

lib/jsonapi/active_relation_retrieval_v10.rb

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ def find_related_ids(relationship, options)
99
end
1010

1111
module ClassMethods
12+
def default_find_related_through(polymorphic = false)
13+
polymorphic ? :primary : :primary
14+
end
15+
1216
# Finds Resources using the `filters`. Pagination and sort options are used when provided
1317
#
1418
# @param filters [Hash] the filters hash
@@ -20,9 +24,9 @@ module ClassMethods
2024
def find(filters, options)
2125
sort_criteria = options.fetch(:sort_criteria) { [] }
2226

23-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
24-
filters: filters,
25-
sort_criteria: sort_criteria)
27+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
28+
filters: filters,
29+
sort_criteria: sort_criteria)
2630

2731
paginator = options[:paginator]
2832

@@ -42,8 +46,8 @@ def find(filters, options)
4246
#
4347
# @return [Integer] the count
4448
def count(filters, options)
45-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
46-
filters: filters)
49+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
50+
filters: filters)
4751

4852
records = apply_request_settings_to_records(records: records(options),
4953
filters: filters,
@@ -103,11 +107,11 @@ def find_fragments(filters, options)
103107

104108
sort_criteria = options.fetch(:sort_criteria) { [] }
105109

106-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: resource_klass,
107-
source_relationship: nil,
108-
relationships: linkage_relationships.collect(&:name),
109-
sort_criteria: sort_criteria,
110-
filters: filters)
110+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: resource_klass,
111+
source_relationship: nil,
112+
relationships: linkage_relationships.collect(&:name),
113+
sort_criteria: sort_criteria,
114+
filters: filters)
111115

112116
paginator = options[:paginator]
113117

@@ -234,9 +238,9 @@ def count_related(source_resource, relationship, options)
234238
filters = options.fetch(:filters, {})
235239

236240
# Joins in this case are related to the related_klass
237-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
238-
source_relationship: relationship,
239-
filters: filters)
241+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
242+
source_relationship: relationship,
243+
filters: filters)
240244

241245
records = apply_request_settings_to_records(records: records(options),
242246
resource_klass: related_klass,
@@ -377,11 +381,11 @@ def find_related_monomorphic_fragments(source_fragments, relationship, options,
377381
sort_criteria << { field: field, direction: sort[:direction] }
378382
end
379383

380-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
381-
source_relationship: relationship,
382-
relationships: linkage_relationships.collect(&:name),
383-
sort_criteria: sort_criteria,
384-
filters: filters)
384+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
385+
source_relationship: relationship,
386+
relationships: linkage_relationships.collect(&:name),
387+
sort_criteria: sort_criteria,
388+
filters: filters)
385389

386390
paginator = options[:paginator]
387391

@@ -495,10 +499,10 @@ def find_related_polymorphic_fragments(source_fragments, relationship, options,
495499
end
496500
end
497501

498-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
499-
source_relationship: relationship,
500-
relationships: linkage_relationship_paths,
501-
filters: filters)
502+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
503+
source_relationship: relationship,
504+
relationships: linkage_relationship_paths,
505+
filters: filters)
502506

503507
paginator = options[:paginator]
504508

@@ -630,7 +634,7 @@ def find_related_polymorphic_fragments(source_fragments, relationship, options,
630634
end
631635

632636
def apply_request_settings_to_records(records:,
633-
join_manager: ActiveRelation::JoinManagerV10.new(resource_klass: self),
637+
join_manager: ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self),
634638
resource_klass: self,
635639
filters: {},
636640
primary_keys: nil,

lib/jsonapi/configuration.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class Configuration
4545
:default_exclude_links,
4646
:default_resource_retrieval_strategy,
4747
:use_related_resource_records_for_joins,
48+
:default_find_related_through,
49+
:default_find_related_through_polymorphic,
4850
:related_identities_set
4951

5052
def initialize
@@ -174,13 +176,32 @@ def initialize
174176
# per resource (or base resource) using the class method `load_resource_retrieval_strategy`.
175177
#
176178
# Available strategies:
177-
# 'JSONAPI::ActiveRelationRetrieval'
178-
# 'JSONAPI::ActiveRelationRetrievalV09'
179-
# 'JSONAPI::ActiveRelationRetrievalV10'
179+
# 'JSONAPI::ActiveRelationRetrieval' - A configurable retrieval strategy
180+
# 'JSONAPI::ActiveRelationRetrievalV09' - Retrieves resources using the v0.9.x approach. This uses rails'
181+
# `includes` method to retrieve related models. This requires overriding the `records_for` method on the resource
182+
# to control filtering of included resources.
183+
# 'JSONAPI::ActiveRelationRetrievalV10' - Retrieves resources using the v0.10.x approach
184+
# Custom - Specify the a custom retrieval strategy module name as a string
180185
# :none
181186
# :self
182187
self.default_resource_retrieval_strategy = 'JSONAPI::ActiveRelationRetrieval'
183188

189+
# For 'JSONAPI::ActiveRelationRetrieval' we can refine how related resources are retrieved with options for
190+
# monomorphic and polymorphic relationships. The default is :inverse for both.
191+
# :inverse - use the inverse relationship on the related resource. This joins the related resource to the
192+
# primary resource table. To use this a relationship to the primary resource must be defined on the related
193+
# resource.
194+
# :primary - use the primary resource joined with the related resources table. This results in a two phased
195+
# querying approach. The first phase gets the ids and cache fields. The second phase gets any cache misses
196+
# from the related resource. In the second phase permissions are not applied since they were already applied in
197+
# the first phase. This behavior is consistent with JR v0.10.x, with the exception that when caching is disabled
198+
# the retrieval of the primary resources does not need to be done in two phases.
199+
# TODO: Currently this results in `records_for_populate` not being called. We should see if we can fix this by
200+
# merging in `records_for_populate`.
201+
202+
self.default_find_related_through = :inverse
203+
self.default_find_related_through_polymorphic = :inverse
204+
184205
# For 'JSONAPI::ActiveRelationRetrievalV10': use a related resource's `records` when performing joins.
185206
# This setting allows included resources to account for permission scopes. It can be overridden explicitly per
186207
# relationship. Furthermore, specifying a `relation_name` on a relationship will cause this setting to be ignored.
@@ -348,6 +369,10 @@ def allow_include=(allow_include)
348369

349370
attr_writer :use_related_resource_records_for_joins
350371

372+
attr_writer :default_find_related_through
373+
374+
attr_writer :default_find_related_through_polymorphic
375+
351376
attr_writer :related_identities_set
352377
end
353378

lib/jsonapi/relationship.rb

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22

33
module JSONAPI
44
class Relationship
5-
attr_reader :acts_as_set, :foreign_key, :options, :name,
6-
:class_name, :polymorphic, :always_include_optional_linkage_data, :exclude_linkage_data,
7-
:parent_resource, :eager_load_on_include, :custom_methods,
8-
:inverse_relationship, :allow_include, :hidden, :use_related_resource_records_for_joins
9-
10-
attr_writer :allow_include
11-
12-
attr_accessor :_routed, :_warned_missing_route
5+
attr_reader :acts_as_set,
6+
:foreign_key,
7+
:options,
8+
:name,
9+
:class_name,
10+
:polymorphic,
11+
:always_include_optional_linkage_data,
12+
:exclude_linkage_data,
13+
:parent_resource,
14+
:eager_load_on_include,
15+
:custom_methods,
16+
:inverse_relationship,
17+
:hidden,
18+
:use_related_resource_records_for_joins,
19+
:find_related_through
20+
21+
attr_accessor :allow_include,
22+
:_routed,
23+
:_warned_missing_route
1324

1425
def initialize(name, options = {})
1526
@name = name.to_s
@@ -43,6 +54,9 @@ def initialize(name, options = {})
4354
@allow_include = options[:allow_include]
4455
@class_name = nil
4556

57+
find_related_through = options.fetch(:find_related_through, parent_resource_klass&.default_find_related_through)
58+
@find_related_through = find_related_through&.to_sym
59+
4660
@inverse_relationship = options[:inverse_relationship]&.to_sym
4761

4862
@_routed = false
@@ -87,6 +101,10 @@ def inverse_relationship
87101
@inverse_relationship
88102
end
89103

104+
def inverse_relationship_klass
105+
@inverse_relationship_klass ||= resource_klass._relationship(inverse_relationship)
106+
end
107+
90108
def polymorphic_types
91109
return @polymorphic_types if @polymorphic_types
92110

0 commit comments

Comments
 (0)