Skip to content

Commit

Permalink
Allow ActiveRecordRetrieval to support getting related resources thro…
Browse files Browse the repository at this point in the history
…ugh the inverse or primary resources
  • Loading branch information
lgebhardt committed Jan 10, 2024
1 parent 8aa212b commit c829277
Show file tree
Hide file tree
Showing 12 changed files with 482 additions and 158 deletions.
4 changes: 2 additions & 2 deletions lib/jsonapi-resources.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
require 'jsonapi/callbacks'
require 'jsonapi/link_builder'
require 'jsonapi/active_relation/adapters/join_left_active_record_adapter'
require 'jsonapi/active_relation/join_manager'
require 'jsonapi/active_relation/join_manager_v10'
require 'jsonapi/active_relation/join_manager_through_inverse'
require 'jsonapi/active_relation/join_manager_through_primary'
require 'jsonapi/resource_identity'
require 'jsonapi/resource_fragment'
require 'jsonapi/resource_tree'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module ActiveRelation

# Stores relationship paths starting from the resource_klass, consolidating duplicate paths from
# relationships, filters and sorts. When joins are made the table aliases are tracked in join_details
class JoinManager
class JoinManagerThroughInverse
attr_reader :resource_klass,
:source_relationship,
:resource_join_tree,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module ActiveRelation

# Stores relationship paths starting from the resource_klass, consolidating duplicate paths from
# relationships, filters and sorts. When joins are made the table aliases are tracked in join_details
class JoinManagerV10
class JoinManagerThroughPrimary
attr_reader :resource_klass,
:source_relationship,
:resource_join_tree,
Expand Down
387 changes: 330 additions & 57 deletions lib/jsonapi/active_relation_retrieval.rb

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions lib/jsonapi/active_relation_retrieval_v09.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ def records_for(relation_name)
end

module ClassMethods
def default_find_related_through(polymorphic = false)
polymorphic ? :model : :model
end

# Finds Resources using the `filters`. Pagination and sort options are used when provided
#
# @param filters [Hash] the filters hash
Expand Down Expand Up @@ -101,9 +105,9 @@ def find_fragments(filters, options)
sort_criteria = options.fetch(:sort_criteria) { [] }
order_options = construct_order_options(sort_criteria)

join_manager = ActiveRelation::JoinManager.new(resource_klass: self,
filters: filters,
sort_criteria: sort_criteria)
join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self,
filters: filters,
sort_criteria: sort_criteria)

options[:_relation_helper_options] = {
context: context,
Expand Down
50 changes: 27 additions & 23 deletions lib/jsonapi/active_relation_retrieval_v10.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def find_related_ids(relationship, options)
end

module ClassMethods
def default_find_related_through(polymorphic = false)
polymorphic ? :primary : :primary
end

# Finds Resources using the `filters`. Pagination and sort options are used when provided
#
# @param filters [Hash] the filters hash
Expand All @@ -18,9 +22,9 @@ module ClassMethods
def find(filters, options)
sort_criteria = options.fetch(:sort_criteria) { [] }

join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
filters: filters,
sort_criteria: sort_criteria)
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
filters: filters,
sort_criteria: sort_criteria)

paginator = options[:paginator]

Expand All @@ -40,8 +44,8 @@ def find(filters, options)
#
# @return [Integer] the count
def count(filters, options)
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
filters: filters)
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
filters: filters)

records = apply_request_settings_to_records(records: records(options),
filters: filters,
Expand Down Expand Up @@ -101,11 +105,11 @@ def find_fragments(filters, options)

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

join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: resource_klass,
source_relationship: nil,
relationships: linkage_relationships.collect(&:name),
sort_criteria: sort_criteria,
filters: filters)
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: resource_klass,
source_relationship: nil,
relationships: linkage_relationships.collect(&:name),
sort_criteria: sort_criteria,
filters: filters)

paginator = options[:paginator]

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

# Joins in this case are related to the related_klass
join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
source_relationship: relationship,
filters: filters)
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
source_relationship: relationship,
filters: filters)

records = apply_request_settings_to_records(records: records(options),
resource_klass: related_klass,
Expand Down Expand Up @@ -375,11 +379,11 @@ def find_related_monomorphic_fragments(source_fragments, relationship, options,
sort_criteria << { field: field, direction: sort[:direction] }
end

join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
source_relationship: relationship,
relationships: linkage_relationships.collect(&:name),
sort_criteria: sort_criteria,
filters: filters)
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
source_relationship: relationship,
relationships: linkage_relationships.collect(&:name),
sort_criteria: sort_criteria,
filters: filters)

paginator = options[:paginator]

Expand Down Expand Up @@ -493,10 +497,10 @@ def find_related_polymorphic_fragments(source_fragments, relationship, options,
end
end

join_manager = ActiveRelation::JoinManagerV10.new(resource_klass: self,
source_relationship: relationship,
relationships: linkage_relationship_paths,
filters: filters)
join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self,
source_relationship: relationship,
relationships: linkage_relationship_paths,
filters: filters)

paginator = options[:paginator]

Expand Down Expand Up @@ -628,7 +632,7 @@ def find_related_polymorphic_fragments(source_fragments, relationship, options,
end

def apply_request_settings_to_records(records:,
join_manager: ActiveRelation::JoinManagerV10.new(resource_klass: self),
join_manager: ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self),
resource_klass: self,
filters: {},
primary_keys: nil,
Expand Down
33 changes: 29 additions & 4 deletions lib/jsonapi/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ class Configuration
:resource_cache_usage_report_function,
:default_exclude_links,
:default_resource_retrieval_strategy,
:use_related_resource_records_for_joins
:use_related_resource_records_for_joins,
:default_find_related_through,
:default_find_related_through_polymorphic

def initialize
#:underscored_key, :camelized_key, :dasherized_key, or custom
Expand Down Expand Up @@ -171,13 +173,32 @@ def initialize
# per resource (or base resource) using the class method `load_resource_retrieval_strategy`.
#
# Available strategies:
# 'JSONAPI::ActiveRelationRetrieval'
# 'JSONAPI::ActiveRelationRetrievalV09'
# 'JSONAPI::ActiveRelationRetrievalV10'
# 'JSONAPI::ActiveRelationRetrieval' - A configurable retrieval strategy
# 'JSONAPI::ActiveRelationRetrievalV09' - Retrieves resources using the v0.9.x approach. This uses rails'
# `includes` method to retrieve related models. This requires overriding the `records_for` method on the resource
# to control filtering of included resources.
# 'JSONAPI::ActiveRelationRetrievalV10' - Retrieves resources using the v0.10.x approach
# Custom - Specify the a custom retrieval strategy module name as a string
# :none
# :self
self.default_resource_retrieval_strategy = 'JSONAPI::ActiveRelationRetrieval'

# For 'JSONAPI::ActiveRelationRetrieval' we can refine how related resources are retrieved with options for
# monomorphic and polymorphic relationships. The default is :inverse for both.
# :inverse - use the inverse relationship on the related resource. This joins the related resource to the
# primary resource table. To use this a relationship to the primary resource must be defined on the related
# resource.
# :primary - use the primary resource joined with the related resources table. This results in a two phased
# querying approach. The first phase gets the ids and cache fields. The second phase gets any cache misses
# from the related resource. In the second phase permissions are not applied since they were already applied in
# the first phase. This behavior is consistent with JR v0.10.x, with the exception that when caching is disabled
# the retrieval of the primary resources does not need to be done in two phases.
# TODO: Currently this results in `records_for_populate` not being called. We should see if we can fix this by
# merging in `records_for_populate`.

self.default_find_related_through = :inverse
self.default_find_related_through_polymorphic = :inverse

# For 'JSONAPI::ActiveRelationRetrievalV10': use a related resource's `records` when performing joins.
# This setting allows included resources to account for permission scopes. It can be overridden explicitly per
# relationship. Furthermore, specifying a `relation_name` on a relationship will cause this setting to be ignored.
Expand Down Expand Up @@ -327,6 +348,10 @@ def allow_include=(allow_include)
attr_writer :default_resource_retrieval_strategy

attr_writer :use_related_resource_records_for_joins

attr_writer :default_find_related_through

attr_writer :default_find_related_through_polymorphic
end

class << self
Expand Down
34 changes: 26 additions & 8 deletions lib/jsonapi/relationship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@

module JSONAPI
class Relationship
attr_reader :acts_as_set, :foreign_key, :options, :name,
:class_name, :polymorphic, :always_include_optional_linkage_data, :exclude_linkage_data,
:parent_resource, :eager_load_on_include, :custom_methods,
:inverse_relationship, :allow_include, :hidden, :use_related_resource_records_for_joins

attr_writer :allow_include

attr_accessor :_routed, :_warned_missing_route
attr_reader :acts_as_set,
:foreign_key,
:options,
:name,
:class_name,
:polymorphic,
:always_include_optional_linkage_data,
:exclude_linkage_data,
:parent_resource,
:eager_load_on_include,
:custom_methods,
:inverse_relationship,
:hidden,
:use_related_resource_records_for_joins,
:find_related_through

attr_accessor :allow_include,
:_routed,
:_warned_missing_route

def initialize(name, options = {})
@name = name.to_s
Expand Down Expand Up @@ -42,6 +53,9 @@ def initialize(name, options = {})
@allow_include = options[:allow_include]
@class_name = nil

find_related_through = options.fetch(:find_related_through, parent_resource_klass&.default_find_related_through)
@find_related_through = find_related_through&.to_sym

@inverse_relationship = options[:inverse_relationship]&.to_sym

@_routed = false
Expand Down Expand Up @@ -86,6 +100,10 @@ def inverse_relationship
@inverse_relationship
end

def inverse_relationship_klass
@inverse_relationship_klass ||= resource_klass._relationship(inverse_relationship)
end

def self.polymorphic_types(name)
@poly_hash ||= {}.tap do |hash|
ObjectSpace.each_object do |klass|
Expand Down
Loading

0 comments on commit c829277

Please sign in to comment.