From c8292770a7cf7ae30284bba7ff21947248e119a4 Mon Sep 17 00:00:00 2001 From: lgebhardt Date: Tue, 9 Jan 2024 15:24:14 -0500 Subject: [PATCH] Allow ActiveRecordRetrieval to support getting related resources through the inverse or primary resources --- lib/jsonapi-resources.rb | 4 +- ...ger.rb => join_manager_through_inverse.rb} | 2 +- ...v10.rb => join_manager_through_primary.rb} | 2 +- lib/jsonapi/active_relation_retrieval.rb | 387 +++++++++++++++--- lib/jsonapi/active_relation_retrieval_v09.rb | 10 +- lib/jsonapi/active_relation_retrieval_v10.rb | 50 +-- lib/jsonapi/configuration.rb | 33 +- lib/jsonapi/relationship.rb | 34 +- test/controllers/controller_test.rb | 34 +- test/test_helper.rb | 4 +- .../join_manager_test.rb | 30 +- .../join_manager_v10_test.rb | 50 +-- 12 files changed, 482 insertions(+), 158 deletions(-) rename lib/jsonapi/active_relation/{join_manager.rb => join_manager_through_inverse.rb} (99%) rename lib/jsonapi/active_relation/{join_manager_v10.rb => join_manager_through_primary.rb} (99%) diff --git a/lib/jsonapi-resources.rb b/lib/jsonapi-resources.rb index 5d46099a..b0e5912e 100644 --- a/lib/jsonapi-resources.rb +++ b/lib/jsonapi-resources.rb @@ -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' diff --git a/lib/jsonapi/active_relation/join_manager.rb b/lib/jsonapi/active_relation/join_manager_through_inverse.rb similarity index 99% rename from lib/jsonapi/active_relation/join_manager.rb rename to lib/jsonapi/active_relation/join_manager_through_inverse.rb index 74608b70..0c9414bf 100644 --- a/lib/jsonapi/active_relation/join_manager.rb +++ b/lib/jsonapi/active_relation/join_manager_through_inverse.rb @@ -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, diff --git a/lib/jsonapi/active_relation/join_manager_v10.rb b/lib/jsonapi/active_relation/join_manager_through_primary.rb similarity index 99% rename from lib/jsonapi/active_relation/join_manager_v10.rb rename to lib/jsonapi/active_relation/join_manager_through_primary.rb index 1f92076e..45dac48b 100644 --- a/lib/jsonapi/active_relation/join_manager_v10.rb +++ b/lib/jsonapi/active_relation/join_manager_through_primary.rb @@ -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, diff --git a/lib/jsonapi/active_relation_retrieval.rb b/lib/jsonapi/active_relation_retrieval.rb index 32c4f656..1f3c09f4 100644 --- a/lib/jsonapi/active_relation_retrieval.rb +++ b/lib/jsonapi/active_relation_retrieval.rb @@ -7,6 +7,14 @@ def find_related_ids(relationship, options) end module ClassMethods + def default_find_related_through(polymorphic = false) + if polymorphic + JSONAPI.configuration.default_find_related_through_polymorphic + else + JSONAPI.configuration.default_find_related_through + end + end + # Finds Resources using the `filters`. Pagination and sort options are used when provided # # @param filters [Hash] the filters hash @@ -18,9 +26,9 @@ module ClassMethods def find(filters, options) sort_criteria = options.fetch(: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) paginator = options[:paginator] @@ -40,8 +48,8 @@ def find(filters, options) # # @return [Integer] the count def count(filters, options) - join_manager = ActiveRelation::JoinManager.new(resource_klass: self, - filters: filters) + join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self, + filters: filters) records = apply_request_settings_to_records(records: records(options), filters: filters, @@ -101,11 +109,11 @@ def find_fragments(filters, options) sort_criteria = options.fetch(:sort_criteria) { [] } - join_manager = ActiveRelation::JoinManager.new(resource_klass: resource_klass, - source_relationship: nil, - relationships: linkage_relationships.collect(&:name), - sort_criteria: sort_criteria, - filters: filters) + join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: resource_klass, + source_relationship: nil, + relationships: linkage_relationships.collect(&:name), + sort_criteria: sort_criteria, + filters: filters) paginator = options[:paginator] @@ -258,54 +266,319 @@ def find_fragments(filters, options) # the ResourceInstances matching the filters, sorting, and pagination rules along with any request # additional_field values def find_related_fragments(source_fragment, relationship, options) - if relationship.polymorphic? # && relationship.foreign_key_on == :self - source_resource_klasses = if relationship.foreign_key_on == :self - relationship.class.polymorphic_types(relationship.name).collect do |polymorphic_type| - resource_klass_for(polymorphic_type) - end - else - source.collect { |fragment| fragment.identity.resource_klass }.to_set - end + _find_related_fragments([source_fragment], relationship, false, options) + end - fragments = {} - source_resource_klasses.each do |resource_klass| - inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize) + def find_included_fragments(source_fragments, relationship, options) + _find_related_fragments(source_fragments, relationship, true, options) + end - fragments.merge!(resource_klass.find_related_fragments_from_inverse([source_fragment], inverse_direct_relationship, options, true)) + def _find_related_fragments(source_fragments, relationship, connect_to_source, options) + if relationship.find_related_through == :inverse + if relationship.polymorphic? + source_resource_klasses = if relationship.foreign_key_on == :self + relationship.class.polymorphic_types(relationship.name).collect do |polymorphic_type| + resource_klass_for(polymorphic_type) + end + else + source_fragments.collect { |fragment| fragment.identity.resource_klass }.to_set + end + fragments = {} + source_resource_klasses.each do |resource_klass| + inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize) + inverse_relationship_klass = inverse_direct_relationship.inverse_relationship_klass + + fragments.merge!(resource_klass._find_related_fragments_through_inverse(source_fragments, inverse_relationship_klass, options, connect_to_source)) + end + fragments + else + inverse_relationship_klass = relationship.inverse_relationship_klass + if inverse_relationship_klass.present? + relationship.resource_klass._find_related_fragments_through_inverse(source_fragments, inverse_relationship_klass, options, connect_to_source) + else + raise "missing inverse relationship" + end + end + elsif relationship.find_related_through == :primary + if relationship.polymorphic? + _find_related_polymorphic_fragments_through_primary(source_fragments, relationship, options, connect_to_source) + else + _find_related_monomorphic_fragments_through_primary(source_fragments, relationship, options, connect_to_source) end - fragments else - relationship.resource_klass.find_related_fragments_from_inverse([source_fragment], relationship, options, false) + raise "find_related_through: #{relationship.find_related_through} not supported" end end - def find_included_fragments(source_fragments, relationship, options) - if relationship.polymorphic? # && relationship.foreign_key_on == :self - source_resource_klasses = if relationship.foreign_key_on == :self - relationship.class.polymorphic_types(relationship.name).collect do |polymorphic_type| - resource_klass_for(polymorphic_type) - end - else - source_fragments.collect { |fragment| fragment.identity.resource_klass }.to_set - end + def _find_related_monomorphic_fragments_through_primary(source_fragments, relationship, options, connect_source_identity) + filters = options.fetch(:filters, {}) + source_ids = source_fragments.collect {|item| item.identity.id} + + include_directives = options.fetch(:include_directives, {}) + resource_klass = relationship.resource_klass + linkage_relationships = resource_klass.to_one_relationships_for_linkage(include_directives[:include_related]) + + sort_criteria = [] + options[:sort_criteria].try(:each) do |sort| + field = sort[:field].to_s == 'id' ? resource_klass._primary_key : sort[:field] + sort_criteria << { field: field, direction: sort[:direction] } + end - fragments = {} - source_resource_klasses.each do |resource_klass| - inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize) + 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] + + records = apply_request_settings_to_records(records: records_for_source_to_related(options), + resource_klass: resource_klass, + sort_criteria: sort_criteria, + primary_keys: source_ids, + paginator: paginator, + filters: filters, + join_manager: join_manager, + options: options) - fragments.merge!(resource_klass.find_related_fragments_from_inverse(source_fragments, inverse_direct_relationship, options, true)) + resource_table_alias = join_manager.join_details_by_relationship(relationship)[:alias] + + control_fields = [ + Arel.sql("#{_table_name}.#{_primary_key} AS \"source_id\""), + sql_field_with_alias(resource_table_alias, resource_klass._primary_key) + ] + + cache_field = resource_klass.attribute_to_model_field(:_cache_field) if options[:cache] + if cache_field + control_fields << sql_field_with_alias(resource_table_alias, cache_field[:name]) + end + + linkage_fields = [] + + linkage_relationships.each do |linkage_relationship| + linkage_relationship_name = linkage_relationship.name + + if linkage_relationship.polymorphic? && linkage_relationship.belongs_to? + linkage_relationship.resource_types.each do |resource_type| + klass = resource_klass_for(resource_type) + linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass} + + linkage_table_alias = join_manager.join_details_by_polymorphic_relationship(linkage_relationship, resource_type)[:alias] + primary_key = klass._primary_key + control_fields << sql_field_with_alias(linkage_table_alias, primary_key) + end + else + klass = linkage_relationship.resource_klass + linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass} + + linkage_table_alias = join_manager.join_details_by_relationship(linkage_relationship)[:alias] + primary_key = klass._primary_key + control_fields << sql_field_with_alias(linkage_table_alias, primary_key) end - fragments - else - relationship.resource_klass.find_related_fragments_from_inverse(source_fragments, relationship, options, true) end + + sort_fields = options.dig(:_relation_helper_options, :sort_fields) + sort_fields.try(:each) do |field| + control_fields << Arel.sql(field) + end + + fragments = {} + + rows = records.distinct.pluck(*control_fields) + rows.each do |row| + rid = JSONAPI::ResourceIdentity.new(resource_klass, row[1]) + + fragments[rid] ||= JSONAPI::ResourceFragment.new(rid) + + attributes_offset = 2 + + if cache_field + fragments[rid].cache = cast_to_attribute_type(row[attributes_offset], cache_field[:type]) + attributes_offset+= 1 + end + + source_rid = JSONAPI::ResourceIdentity.new(self, row[0]) + + fragments[rid].add_related_from(source_rid) + + linkage_fields.each do |linkage_field| + fragments[rid].initialize_related(linkage_field[:relationship_name]) + related_id = row[attributes_offset] + if related_id + related_rid = JSONAPI::ResourceIdentity.new(linkage_field[:resource_klass], related_id) + fragments[rid].add_related_identity(linkage_field[:relationship_name], related_rid) + end + attributes_offset+= 1 + end + + if connect_source_identity + related_relationship = resource_klass._relationship(relationship.inverse_relationship) + if related_relationship + fragments[rid].add_related_identity(related_relationship.name, source_rid) + end + end + end + + fragments end - def find_related_fragments_from_inverse(source, source_relationship, options, connect_source_identity) - relationship = source_relationship.resource_klass._relationship(source_relationship.inverse_relationship) - raise "missing inverse relationship" unless relationship.present? + # Gets resource identities where the related resource is polymorphic and the resource type and id + # are stored on the primary resources. Cache fields will always be on the related resources. + def _find_related_polymorphic_fragments_through_primary(source_fragments, relationship, options, connect_source_identity) + filters = options.fetch(:filters, {}) + source_ids = source_fragments.collect {|item| item.identity.id} + + resource_klass = relationship.resource_klass + include_directives = options.fetch(:include_directives, {}) + + linkage_relationship_paths = [] + + resource_types = relationship.resource_types + + resource_types.each do |resource_type| + related_resource_klass = resource_klass_for(resource_type) + relationships = related_resource_klass.to_one_relationships_for_linkage(include_directives[:include_related]) + relationships.each do |r| + linkage_relationship_paths << "##{resource_type}.#{r.name}" + end + end + + join_manager = ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: self, + source_relationship: relationship, + relationships: linkage_relationship_paths, + filters: filters) + + paginator = options[:paginator] + + # Note: We will sort by the source table. Without using unions we can't sort on a polymorphic relationship + # in any manner that makes sense + records = apply_request_settings_to_records(records: records_for_source_to_related(options), + resource_klass: resource_klass, + sort_primary: true, + primary_keys: source_ids, + paginator: paginator, + filters: filters, + join_manager: join_manager, + options: options) + + primary_key = concat_table_field(_table_name, _primary_key) + related_key = concat_table_field(_table_name, relationship.foreign_key) + related_type = concat_table_field(_table_name, relationship.polymorphic_type) + + pluck_fields = [ + Arel.sql("#{primary_key} AS #{alias_table_field(_table_name, _primary_key)}"), + Arel.sql("#{related_key} AS #{alias_table_field(_table_name, relationship.foreign_key)}"), + Arel.sql("#{related_type} AS #{alias_table_field(_table_name, relationship.polymorphic_type)}") + ] + + # Get the additional fields from each relation. There's a limitation that the fields must exist in each relation + + relation_positions = {} + relation_index = pluck_fields.length + + # Add resource specific fields + if resource_types.nil? || resource_types.length == 0 + # :nocov: + warn "No resource types found for polymorphic relationship." + # :nocov: + else + resource_types.try(:each) do |type| + related_klass = resource_klass_for(type.to_s) + + cache_field = related_klass.attribute_to_model_field(:_cache_field) if options[:cache] + + table_alias = join_manager.source_join_details(type)[:alias] + + cache_offset = relation_index + if cache_field + pluck_fields << sql_field_with_alias(table_alias, cache_field[:name]) + relation_index+= 1 + end + + relation_positions[type] = {relation_klass: related_klass, + cache_field: cache_field, + cache_offset: cache_offset} + end + end + + # Add to_one linkage fields + linkage_fields = [] + linkage_offset = relation_index + + linkage_relationship_paths.each do |linkage_relationship_path| + path = JSONAPI::Path.new(resource_klass: self, + path_string: "#{relationship.name}#{linkage_relationship_path}", + ensure_default_field: false) + + linkage_relationship = path.segments[-1].relationship + + if linkage_relationship.polymorphic? && linkage_relationship.belongs_to? + linkage_relationship.resource_types.each do |resource_type| + klass = resource_klass_for(resource_type) + linkage_fields << {relationship: linkage_relationship, resource_klass: klass} + + linkage_table_alias = join_manager.join_details_by_polymorphic_relationship(linkage_relationship, resource_type)[:alias] + primary_key = klass._primary_key + pluck_fields << sql_field_with_alias(linkage_table_alias, primary_key) + end + else + klass = linkage_relationship.resource_klass + linkage_fields << {relationship: linkage_relationship, resource_klass: klass} + + linkage_table_alias = join_manager.join_details_by_relationship(linkage_relationship)[:alias] + primary_key = klass._primary_key + pluck_fields << sql_field_with_alias(linkage_table_alias, primary_key) + end + end + + rows = records.distinct.pluck(*pluck_fields) + + related_fragments = {} + + rows.each do |row| + unless row[1].nil? || row[2].nil? + related_klass = resource_klass_for(row[2]) + + rid = JSONAPI::ResourceIdentity.new(related_klass, row[1]) + related_fragments[rid] ||= JSONAPI::ResourceFragment.new(rid) + + source_rid = JSONAPI::ResourceIdentity.new(self, row[0]) + related_fragments[rid].add_related_from(source_rid) + + if connect_source_identity + related_relationship = related_klass._relationship(relationship.inverse_relationship) + if related_relationship + related_fragments[rid].add_related_identity(related_relationship.name, source_rid) + end + end + + relation_position = relation_positions[row[2].underscore.pluralize] + model_fields = relation_position[:model_fields] + cache_field = relation_position[:cache_field] + cache_offset = relation_position[:cache_offset] + field_offset = relation_position[:field_offset] + + if cache_field + related_fragments[rid].cache = cast_to_attribute_type(row[cache_offset], cache_field[:type]) + end + + linkage_fields.each_with_index do |linkage_field_details, idx| + relationship = linkage_field_details[:relationship] + related_fragments[rid].initialize_related(relationship.name) + related_id = row[linkage_offset + idx] + if related_id + related_rid = JSONAPI::ResourceIdentity.new(linkage_field_details[:resource_klass], related_id) + related_fragments[rid].add_related_identity(relationship.name, related_rid) + end + end + end + end + + related_fragments + end - parent_resource_klass = relationship.resource_klass + def _find_related_fragments_through_inverse(source, inverse_relationship_klass, options, connect_source_identity) + parent_resource_klass = inverse_relationship_klass.resource_klass include_directives = options.fetch(:include_directives, {}) @@ -322,11 +595,11 @@ def find_related_fragments_from_inverse(source, source_relationship, options, co sort_criteria << { field: field, direction: sort[:direction] } end - join_manager = ActiveRelation::JoinManager.new(resource_klass: self, - source_relationship: relationship, - relationships: linkage_relationships.collect(&:name), - sort_criteria: sort_criteria, - filters: filters) + join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self, + source_relationship: inverse_relationship_klass, + relationships: linkage_relationships.collect(&:name), + sort_criteria: sort_criteria, + filters: filters) paginator = options[:paginator] @@ -343,7 +616,7 @@ def find_related_fragments_from_inverse(source, source_relationship, options, co if options[:cache] # This alias is going to be resolve down to the model's table name and will not actually be an alias resource_table_alias = self._table_name - parent_table_alias = join_manager.join_details_by_relationship(relationship)[:alias] + parent_table_alias = join_manager.join_details_by_relationship(inverse_relationship_klass)[:alias] pluck_fields = [ sql_field_with_alias(resource_table_alias, self._primary_key), @@ -391,7 +664,7 @@ def find_related_fragments_from_inverse(source, source_relationship, options, co fragments[rid].add_related_from(parent_rid) if connect_source_identity - fragments[rid].add_related_identity(relationship.name, parent_rid) + fragments[rid].add_related_identity(inverse_relationship_klass.name, parent_rid) end attributes_offset = 2 @@ -443,7 +716,7 @@ def find_related_fragments_from_inverse(source, source_relationship, options, co end end - parent_table_alias = join_manager.join_details_by_relationship(relationship)[:alias] + parent_table_alias = join_manager.join_details_by_relationship(inverse_relationship_klass)[:alias] source_field = sql_field_with_fixed_alias(parent_table_alias, parent_resource_klass._primary_key, "jr_source_id") records = records.select(concat_table_field(_table_name, Arel.star), source_field) @@ -462,7 +735,7 @@ def find_related_fragments_from_inverse(source, source_relationship, options, co parent_rid = JSONAPI::ResourceIdentity.new(parent_resource_klass, resource._model.attributes['jr_source_id']) if connect_source_identity - fragments[rid].add_related_identity(relationship.name, parent_rid) + fragments[rid].add_related_identity(inverse_relationship_klass.name, parent_rid) end fragments[rid].add_related_from(parent_rid) @@ -501,9 +774,9 @@ def count_related_from_inverse(source_resource, source_relationship, options) filters = options.fetch(:filters, {}) # Joins in this case are related to the related_klass - join_manager = ActiveRelation::JoinManager.new(resource_klass: self, - source_relationship: relationship, - filters: filters) + join_manager = ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self, + source_relationship: relationship, + filters: filters) records = apply_request_settings_to_records(records: records(options), resource_klass: self, @@ -638,7 +911,7 @@ def find_records_by_keys(keys, options) end def apply_request_settings_to_records(records:, - join_manager: ActiveRelation::JoinManager.new(resource_klass: self), + join_manager: ActiveRelation::JoinManagerThroughInverse.new(resource_klass: self), resource_klass: self, source_ids: nil, filters: {}, diff --git a/lib/jsonapi/active_relation_retrieval_v09.rb b/lib/jsonapi/active_relation_retrieval_v09.rb index 6238afb3..a5152366 100644 --- a/lib/jsonapi/active_relation_retrieval_v09.rb +++ b/lib/jsonapi/active_relation_retrieval_v09.rb @@ -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 @@ -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, diff --git a/lib/jsonapi/active_relation_retrieval_v10.rb b/lib/jsonapi/active_relation_retrieval_v10.rb index f5f84e21..4c9069e0 100644 --- a/lib/jsonapi/active_relation_retrieval_v10.rb +++ b/lib/jsonapi/active_relation_retrieval_v10.rb @@ -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 @@ -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] @@ -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, @@ -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] @@ -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, @@ -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] @@ -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] @@ -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, diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index 1d2c235e..535b00e1 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -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 @@ -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. @@ -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 diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 31a053b2..abaf8038 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -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 @@ -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 @@ -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| diff --git a/test/controllers/controller_test.rb b/test/controllers/controller_test.rb index 12e3cbcc..c669a23c 100644 --- a/test/controllers/controller_test.rb +++ b/test/controllers/controller_test.rb @@ -296,7 +296,7 @@ def test_index_filter_not_allowed end def test_index_include_one_level_query_count - assert_query_count(testing_v10? ? 4 : 2) do + assert_query_count(through_primary? ? 4 : 2) do assert_cacheable_get :index, params: {include: 'author'} end @@ -304,7 +304,7 @@ def test_index_include_one_level_query_count end def test_index_include_two_levels_query_count - assert_query_count(testing_v10? ? 6 : 3) do + assert_query_count(through_primary? ? 6 : 3) do assert_cacheable_get :index, params: { include: 'author,author.comments' } end assert_response :success @@ -355,7 +355,7 @@ def test_index_filter_by_ids_and_fields_2 end def test_filter_relationship_single - assert_query_count(testing_v10? ? 2 : 1) do + assert_query_count(through_primary? ? 2 : 1) do assert_cacheable_get :index, params: {filter: {tags: '505,501'}} end assert_response :success @@ -366,7 +366,7 @@ def test_filter_relationship_single end def test_filter_relationships_multiple - assert_query_count(testing_v10? ? 2 : 1) do + assert_query_count(through_primary? ? 2 : 1) do assert_cacheable_get :index, params: { filter: { tags: '505,501', comments: '3' } } end assert_response :success @@ -3303,7 +3303,7 @@ def test_books_offset_pagination_no_params_includes_query_count_one_level with_jsonapi_config_changes do JSONAPI.configuration.json_key_format = :dasherized_key - assert_query_count(testing_v10? ? 5 : 3) do + assert_query_count(through_primary? ? 5 : 3) do assert_cacheable_get :index, params: { include: 'book-comments' } end assert_response :success @@ -3317,7 +3317,7 @@ def test_books_offset_pagination_no_params_includes_query_count_two_levels with_jsonapi_config_changes do JSONAPI.configuration.json_key_format = :dasherized_key - assert_query_count(testing_v10? ? 7 : 4) do + assert_query_count(through_primary? ? 7 : 4) do assert_cacheable_get :index, params: { include: 'book-comments,book-comments.author' } end assert_response :success @@ -3451,7 +3451,7 @@ def test_books_included_paged with_jsonapi_config_changes do JSONAPI.configuration.json_key_format = :dasherized_key - assert_query_count(testing_v10? ? 5 : 3) do + assert_query_count(through_primary? ? 5 : 3) do assert_cacheable_get :index, params: { filter: { id: '0' }, include: 'book-comments' } assert_response :success assert_equal 1, json_response['data'].size @@ -3468,7 +3468,7 @@ def test_books_banned_non_book_admin JSONAPI.configuration.top_level_meta_include_record_count = true JSONAPI.configuration.json_key_format = :dasherized_key - assert_query_count(testing_v10? ? 3 : 2) do + assert_query_count(through_primary? ? 3 : 2) do assert_cacheable_get :index, params: { page: { offset: 50, limit: 12 } } assert_response :success assert_equal 12, json_response['data'].size @@ -3485,7 +3485,7 @@ def test_books_banned_non_book_admin_includes_switched Api::V2::BookResource.paginator :offset JSONAPI.configuration.top_level_meta_include_record_count = true - assert_query_count(testing_v10? ? 5 : 3) do + assert_query_count(through_primary? ? 5 : 3) do assert_cacheable_get :index, params: { page: { offset: 0, limit: 12 }, include: 'book-comments' } assert_response :success assert_equal 12, json_response['data'].size @@ -3504,7 +3504,7 @@ def test_books_banned_non_book_admin_includes_nested_includes JSONAPI.configuration.json_key_format = :dasherized_key JSONAPI.configuration.top_level_meta_include_record_count = true Api::V2::BookResource.paginator :offset - assert_query_count(testing_v10? ? 7 : 4) do + assert_query_count(through_primary? ? 7 : 4) do assert_cacheable_get :index, params: { page: { offset: 0, limit: 12 }, include: 'book-comments.author' } assert_response :success assert_equal 12, json_response['data'].size @@ -3522,7 +3522,7 @@ def test_books_banned_admin Api::V2::BookResource.paginator :offset JSONAPI.configuration.json_key_format = :dasherized_key JSONAPI.configuration.top_level_meta_include_record_count = true - assert_query_count(testing_v10? ? 3 : 2) do + assert_query_count(through_primary? ? 3 : 2) do assert_cacheable_get :index, params: { page: { offset: 50, limit: 12 }, filter: { banned: 'true' } } end assert_response :success @@ -3539,7 +3539,7 @@ def test_books_not_banned_admin Api::V2::BookResource.paginator :offset JSONAPI.configuration.json_key_format = :dasherized_key JSONAPI.configuration.top_level_meta_include_record_count = true - assert_query_count(testing_v10? ? 3 : 2) do + assert_query_count(through_primary? ? 3 : 2) do assert_cacheable_get :index, params: { page: { offset: 50, limit: 12 }, filter: { banned: 'false' }, fields: { books: 'id,title' } } end assert_response :success @@ -3557,7 +3557,7 @@ def test_books_banned_non_book_admin_overlapped Api::V2::BookResource.paginator :offset JSONAPI.configuration.top_level_meta_include_record_count = true - assert_query_count(testing_v10? ? 3 : 2) do + assert_query_count(through_primary? ? 3 : 2) do assert_cacheable_get :index, params: { page: { offset: 590, limit: 20 } } end assert_response :success @@ -3574,7 +3574,7 @@ def test_books_included_exclude_unapproved with_jsonapi_config_changes do JSONAPI.configuration.json_key_format = :dasherized_key - assert_query_count(testing_v10? ? 4 : 2) do + assert_query_count(through_primary? ? 4 : 2) do assert_cacheable_get :index, params: { filter: { id: '0,1,2,3,4' }, include: 'book-comments' } end assert_response :success @@ -3695,7 +3695,7 @@ def setup def test_book_comments_all_for_admin $test_user = Person.find(1005) - assert_query_count(testing_v10? ? 2 : 1) do + assert_query_count(through_primary? ? 2 : 1) do assert_cacheable_get :index end assert_response :success @@ -3704,7 +3704,7 @@ def test_book_comments_all_for_admin def test_book_comments_unapproved_context_based $test_user = Person.find(1005) - assert_query_count(testing_v10? ? 2 : 1) do + assert_query_count(through_primary? ? 2 : 1) do assert_cacheable_get :index, params: { filter: { approved: 'false' } } end assert_response :success @@ -3713,7 +3713,7 @@ def test_book_comments_unapproved_context_based def test_book_comments_exclude_unapproved_context_based $test_user = Person.find(1001) - assert_query_count(testing_v10? ? 2 : 1) do + assert_query_count(through_primary? ? 2 : 1) do assert_cacheable_get :index end assert_response :success diff --git a/test/test_helper.rb b/test/test_helper.rb index 834c8577..0be54c10 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -675,8 +675,8 @@ def assert_cacheable_get(action, **args) @queries = orig_queries end - def testing_v10? - JSONAPI.configuration.default_resource_retrieval_strategy == 'JSONAPI::ActiveRelationRetrievalV10' + def through_primary? + JSONAPI.configuration.default_find_related_through == :primary end def testing_v09? diff --git a/test/unit/active_relation_resource_finder/join_manager_test.rb b/test/unit/active_relation_resource_finder/join_manager_test.rb index 53799e9e..b340229f 100644 --- a/test/unit/active_relation_resource_finder/join_manager_test.rb +++ b/test/unit/active_relation_resource_finder/join_manager_test.rb @@ -11,7 +11,7 @@ class JoinManagerTest < ActiveSupport::TestCase # end def test_no_added_joins - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -22,7 +22,7 @@ def test_no_added_joins def test_add_single_join filters = {'tags' => ['1']} - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', sql_for_compare(records.to_sql) @@ -32,7 +32,7 @@ def test_add_single_join def test_add_single_sort_join sort_criteria = [{field: 'tags.name', direction: :desc}] - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, sort_criteria: sort_criteria) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource, sort_criteria: sort_criteria) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -44,7 +44,7 @@ def test_add_single_sort_join def test_add_single_sort_and_filter_join filters = {'tags' => ['1']} sort_criteria = [{field: 'tags.name', direction: :desc}] - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, sort_criteria: sort_criteria, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource, sort_criteria: sort_criteria, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', sql_for_compare(records.to_sql) @@ -58,7 +58,7 @@ def test_add_sibling_joins 'author' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -70,7 +70,7 @@ def test_add_sibling_joins def test_add_joins_source_relationship - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource, source_relationship: PostResource._relationship(:comments)) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -81,7 +81,7 @@ def test_add_joins_source_relationship def test_add_joins_source_relationship_with_custom_apply - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: Api::V10::PostResource, + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: Api::V10::PostResource, source_relationship: Api::V10::PostResource._relationship(:comments)) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -100,7 +100,7 @@ def test_add_nested_scoped_joins 'author' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: Api::V10::PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: Api::V10::PostResource, filters: filters) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -117,7 +117,7 @@ def test_add_nested_scoped_joins 'comments.tags' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: Api::V10::PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: Api::V10::PostResource, filters: filters) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -135,7 +135,7 @@ def test_add_nested_joins_with_fields 'author.foo' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: Api::V10::PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: Api::V10::PostResource, filters: filters) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -149,7 +149,7 @@ def test_add_nested_joins_with_fields def test_add_joins_with_sub_relationship relationships = %w(author author.comments tags) - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: Api::V10::PostResource, relationships: relationships, + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: Api::V10::PostResource, relationships: relationships, source_relationship: Api::V10::PostResource._relationship(:comments)) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -168,7 +168,7 @@ def test_add_joins_with_sub_relationship_and_filters relationships = %w(author author.comments tags) - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PostResource, filters: filters, relationships: relationships, source_relationship: PostResource._relationship(:comments)) @@ -183,7 +183,7 @@ def test_add_joins_with_sub_relationship_and_filters end def test_polymorphic_join_belongs_to_just_source - join_manager = JSONAPI::ActiveRelation::JoinManager.new( + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new( resource_klass: PictureResource, source_relationship: PictureResource._relationship(:imageable) ) @@ -200,7 +200,7 @@ def test_polymorphic_join_belongs_to_just_source def test_polymorphic_join_belongs_to_filter filters = {'imageable' => ['Foo']} - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PictureResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PictureResource, filters: filters) records = PictureResource.records({}) records = join_manager.join(records, {}) @@ -217,7 +217,7 @@ def test_polymorphic_join_belongs_to_filter_on_resource } relationships = %w(imageable file_properties) - join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PictureResource, + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughInverse.new(resource_klass: PictureResource, filters: filters, relationships: relationships) diff --git a/test/unit/active_relation_resource_finder/join_manager_v10_test.rb b/test/unit/active_relation_resource_finder/join_manager_v10_test.rb index d0ec9a29..731c6bf1 100644 --- a/test/unit/active_relation_resource_finder/join_manager_v10_test.rb +++ b/test/unit/active_relation_resource_finder/join_manager_v10_test.rb @@ -3,7 +3,7 @@ class JoinManagerV10Test < ActiveSupport::TestCase def test_no_added_joins - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -14,7 +14,7 @@ def test_no_added_joins def test_add_single_join filters = {'tags' => ['1']} - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', sql_for_compare(records.to_sql) @@ -24,7 +24,7 @@ def test_add_single_join def test_joins_have_join_options filters = {'tags' => ['1']} - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', sql_for_compare(records.to_sql) @@ -38,7 +38,7 @@ def test_joins_have_join_options def test_add_single_sort_join sort_criteria = [{field: 'tags.name', direction: :desc}] - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, sort_criteria: sort_criteria) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, sort_criteria: sort_criteria) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -50,7 +50,7 @@ def test_add_single_sort_join def test_add_single_sort_and_filter_join filters = {'tags' => ['1']} sort_criteria = [{field: 'tags.name', direction: :desc}] - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, sort_criteria: sort_criteria, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, sort_criteria: sort_criteria, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', sql_for_compare(records.to_sql) @@ -64,7 +64,7 @@ def test_add_sibling_joins 'author' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, filters: filters) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -76,8 +76,8 @@ def test_add_sibling_joins def test_add_joins_source_relationship - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, - source_relationship: PostResource._relationship(:comments)) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, + source_relationship: PostResource._relationship(:comments)) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -87,8 +87,8 @@ def test_add_joins_source_relationship def test_add_joins_source_relationship_with_custom_apply - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: Api::V10::PostResource, - source_relationship: Api::V10::PostResource._relationship(:comments)) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: Api::V10::PostResource, + source_relationship: Api::V10::PostResource._relationship(:comments)) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -106,7 +106,7 @@ def test_add_nested_scoped_joins 'author' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: Api::V10::PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: Api::V10::PostResource, filters: filters) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -123,7 +123,7 @@ def test_add_nested_scoped_joins 'comments.tags' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: Api::V10::PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: Api::V10::PostResource, filters: filters) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -141,7 +141,7 @@ def test_add_nested_joins_with_fields 'author.foo' => ['1'] } - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: Api::V10::PostResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: Api::V10::PostResource, filters: filters) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -155,8 +155,8 @@ def test_add_nested_joins_with_fields def test_add_joins_with_sub_relationship relationships = %w(author author.comments tags) - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: Api::V10::PostResource, relationships: relationships, - source_relationship: Api::V10::PostResource._relationship(:comments)) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: Api::V10::PostResource, relationships: relationships, + source_relationship: Api::V10::PostResource._relationship(:comments)) records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) @@ -174,10 +174,10 @@ def test_add_joins_with_sub_relationship_and_filters relationships = %w(author author.comments tags) - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PostResource, - filters: filters, - relationships: relationships, - source_relationship: PostResource._relationship(:comments)) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PostResource, + filters: filters, + relationships: relationships, + source_relationship: PostResource._relationship(:comments)) records = PostResource.records({}) records = join_manager.join(records, {}) @@ -189,8 +189,8 @@ def test_add_joins_with_sub_relationship_and_filters end def test_polymorphic_join_belongs_to_just_source - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PictureResource, - source_relationship: PictureResource._relationship(:imageable)) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PictureResource, + source_relationship: PictureResource._relationship(:imageable)) records = PictureResource.records({}) records = join_manager.join(records, {}) @@ -204,7 +204,7 @@ def test_polymorphic_join_belongs_to_just_source def test_polymorphic_join_belongs_to_filter filters = {'imageable' => ['Foo']} - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PictureResource, filters: filters) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PictureResource, filters: filters) records = PictureResource.records({}) records = join_manager.join(records, {}) @@ -221,9 +221,9 @@ def test_polymorphic_join_belongs_to_filter_on_resource } relationships = %w(imageable file_properties) - join_manager = JSONAPI::ActiveRelation::JoinManagerV10.new(resource_klass: PictureResource, - filters: filters, - relationships: relationships) + join_manager = JSONAPI::ActiveRelation::JoinManagerThroughPrimary.new(resource_klass: PictureResource, + filters: filters, + relationships: relationships) records = PictureResource.records({}) records = join_manager.join(records, {})