Skip to content

Commit c829277

Browse files
committed
Allow ActiveRecordRetrieval to support getting related resources through the inverse or primary resources
1 parent 8aa212b commit c829277

12 files changed

+482
-158
lines changed

lib/jsonapi-resources.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
require 'jsonapi/callbacks'
4040
require 'jsonapi/link_builder'
4141
require 'jsonapi/active_relation/adapters/join_left_active_record_adapter'
42-
require 'jsonapi/active_relation/join_manager'
43-
require 'jsonapi/active_relation/join_manager_v10'
42+
require 'jsonapi/active_relation/join_manager_through_inverse'
43+
require 'jsonapi/active_relation/join_manager_through_primary'
4444
require 'jsonapi/resource_identity'
4545
require 'jsonapi/resource_fragment'
4646
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
@@ -13,6 +13,10 @@ def records_for(relation_name)
1313
end
1414

1515
module ClassMethods
16+
def default_find_related_through(polymorphic = false)
17+
polymorphic ? :model : :model
18+
end
19+
1620
# Finds Resources using the `filters`. Pagination and sort options are used when provided
1721
#
1822
# @param filters [Hash] the filters hash
@@ -101,9 +105,9 @@ def find_fragments(filters, options)
101105
sort_criteria = options.fetch(:sort_criteria) { [] }
102106
order_options = construct_order_options(sort_criteria)
103107

104-
join_manager = ActiveRelation::JoinManager.new(resource_klass: self,
105-
filters: filters,
106-
sort_criteria: sort_criteria)
108+
join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self,
109+
filters: filters,
110+
sort_criteria: sort_criteria)
107111

108112
options[:_relation_helper_options] = {
109113
context: context,

lib/jsonapi/active_relation_retrieval_v10.rb

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

99
module ClassMethods
10+
def default_find_related_through(polymorphic = false)
11+
polymorphic ? :primary : :primary
12+
end
13+
1014
# Finds Resources using the `filters`. Pagination and sort options are used when provided
1115
#
1216
# @param filters [Hash] the filters hash
@@ -18,9 +22,9 @@ module ClassMethods
1822
def find(filters, options)
1923
sort_criteria = options.fetch(:sort_criteria) { [] }
2024

21-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
22-
filters: filters,
23-
sort_criteria: sort_criteria)
25+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
26+
filters: filters,
27+
sort_criteria: sort_criteria)
2428

2529
paginator = options[:paginator]
2630

@@ -40,8 +44,8 @@ def find(filters, options)
4044
#
4145
# @return [Integer] the count
4246
def count(filters, options)
43-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
44-
filters: filters)
47+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
48+
filters: filters)
4549

4650
records = apply_request_settings_to_records(records: records(options),
4751
filters: filters,
@@ -101,11 +105,11 @@ def find_fragments(filters, options)
101105

102106
sort_criteria = options.fetch(:sort_criteria) { [] }
103107

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

110114
paginator = options[:paginator]
111115

@@ -232,9 +236,9 @@ def count_related(source_resource, relationship, options)
232236
filters = options.fetch(:filters, {})
233237

234238
# Joins in this case are related to the related_klass
235-
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
236-
source_relationship: relationship,
237-
filters: filters)
239+
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
240+
source_relationship: relationship,
241+
filters: filters)
238242

239243
records = apply_request_settings_to_records(records: records(options),
240244
resource_klass: related_klass,
@@ -375,11 +379,11 @@ def find_related_monomorphic_fragments(source_fragments, relationship, options,
375379
sort_criteria << { field: field, direction: sort[:direction] }
376380
end
377381

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

384388
paginator = options[:paginator]
385389

@@ -493,10 +497,10 @@ def find_related_polymorphic_fragments(source_fragments, relationship, options,
493497
end
494498
end
495499

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

501505
paginator = options[:paginator]
502506

@@ -628,7 +632,7 @@ def find_related_polymorphic_fragments(source_fragments, relationship, options,
628632
end
629633

630634
def apply_request_settings_to_records(records:,
631-
join_manager: ActiveRelation::JoinManagerV10.new(resource_klass: self),
635+
join_manager: ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self),
632636
resource_klass: self,
633637
filters: {},
634638
primary_keys: nil,

lib/jsonapi/configuration.rb

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ class Configuration
4343
:resource_cache_usage_report_function,
4444
:default_exclude_links,
4545
:default_resource_retrieval_strategy,
46-
:use_related_resource_records_for_joins
46+
:use_related_resource_records_for_joins,
47+
:default_find_related_through,
48+
:default_find_related_through_polymorphic
4749

4850
def initialize
4951
#:underscored_key, :camelized_key, :dasherized_key, or custom
@@ -171,13 +173,32 @@ def initialize
171173
# per resource (or base resource) using the class method `load_resource_retrieval_strategy`.
172174
#
173175
# Available strategies:
174-
# 'JSONAPI::ActiveRelationRetrieval'
175-
# 'JSONAPI::ActiveRelationRetrievalV09'
176-
# 'JSONAPI::ActiveRelationRetrievalV10'
176+
# 'JSONAPI::ActiveRelationRetrieval' - A configurable retrieval strategy
177+
# 'JSONAPI::ActiveRelationRetrievalV09' - Retrieves resources using the v0.9.x approach. This uses rails'
178+
# `includes` method to retrieve related models. This requires overriding the `records_for` method on the resource
179+
# to control filtering of included resources.
180+
# 'JSONAPI::ActiveRelationRetrievalV10' - Retrieves resources using the v0.10.x approach
181+
# Custom - Specify the a custom retrieval strategy module name as a string
177182
# :none
178183
# :self
179184
self.default_resource_retrieval_strategy = 'JSONAPI::ActiveRelationRetrieval'
180185

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

329350
attr_writer :use_related_resource_records_for_joins
351+
352+
attr_writer :default_find_related_through
353+
354+
attr_writer :default_find_related_through_polymorphic
330355
end
331356

332357
class << self

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
@@ -42,6 +53,9 @@ def initialize(name, options = {})
4253
@allow_include = options[:allow_include]
4354
@class_name = nil
4455

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

4761
@_routed = false
@@ -86,6 +100,10 @@ def inverse_relationship
86100
@inverse_relationship
87101
end
88102

103+
def inverse_relationship_klass
104+
@inverse_relationship_klass ||= resource_klass._relationship(inverse_relationship)
105+
end
106+
89107
def self.polymorphic_types(name)
90108
@poly_hash ||= {}.tap do |hash|
91109
ObjectSpace.each_object do |klass|

0 commit comments

Comments
 (0)