From 074a18743db120e87ae4376cfb40144031a3938d Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 17 Mar 2026 15:07:10 +0100 Subject: [PATCH 1/2] fix(search): exclude polymorphic relations from extended search and fix missing JOIN on list --- .../forest_admin_datasource_active_record/utils/query.rb | 5 +++-- .../utils/query_spec.rb | 6 +++--- .../decorators/search/search_collection_decorator.rb | 7 +++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb index 33034fd6a..ee651a120 100644 --- a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb +++ b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb @@ -211,7 +211,7 @@ def apply_select end def add_join_relation(relation_name) - @query = @query.includes(relation_name.to_sym) + @query = @query.left_joins(relation_name.to_sym) @query end @@ -251,8 +251,9 @@ def build_comparison_query(original_field, arel_attr, value, operator) def query_aggregator(aggregator, query) if !@query.respond_to?(:where_clause) || @query.where_clause.empty? - # Preserve includes from @query when replacing with new query + # Preserve includes and joins from @query when replacing with new query query = query.includes(@query.includes_values) if @query.includes_values.any? + query = query.left_joins(*@query.left_outer_joins_values) if @query.left_outer_joins_values.any? query else @query.send(aggregator, query) diff --git a/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/utils/query_spec.rb b/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/utils/query_spec.rb index 3ee10cfc9..404d3958c 100644 --- a/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/utils/query_spec.rb +++ b/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/utils/query_spec.rb @@ -368,8 +368,8 @@ module Utils query_builder = described_class.new(collection, nil, filter) query_builder.build - # The query should include the category relation - expect(query_builder.query.includes_values).to include(:category) + # The query should left join the category relation + expect(query_builder.query.left_outer_joins_values).to include(:category) end end end @@ -392,7 +392,7 @@ module Utils query_builder = described_class.new(collection, nil, filter) query_builder.build - expect(query_builder.query.includes_values).to include(:category) + expect(query_builder.query.left_outer_joins_values).to include(:category) end end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb index c696d829f..ed70e2b80 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb @@ -5,6 +5,9 @@ class SearchCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::Coll include ForestAdminDatasourceToolkit::Schema include ForestAdminDatasourceToolkit::Components::Query::ConditionTree + POLYMORPHIC_TYPES = %w[PolymorphicManyToOne PolymorphicOneToOne].freeze + TO_ONE_RELATIONS = %w[ManyToOne OneToOne].freeze + def initialize(child_collection, datasource) super @replacer = nil @@ -112,7 +115,7 @@ def get_fields(extended) @child_collection.schema[:fields].each do |name, field| fields.push([name, field]) if field.type == 'Column' && searchable_field?(field) - if field.type == 'PolymorphicManyToOne' && extended + if POLYMORPHIC_TYPES.include?(field.type) && extended ForestAdminAgent::Facades::Container.logger.log( 'Debug', "We're not searching through #{self.name}.#{name} because it's a polymorphic relation. " \ @@ -121,7 +124,7 @@ def get_fields(extended) ) end - to_one_relations = %w[ManyToOne OneToOne PolymorphicOneToOne] + to_one_relations = %w[ManyToOne OneToOne] next unless extended && to_one_relations.include?(field.type) related = @child_collection.datasource.get_collection(field.foreign_collection) From 02d459aef2423871ed7088416dc21c16dc9a01aa Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 17 Mar 2026 15:07:52 +0100 Subject: [PATCH 2/2] chore: add tests --- .../search/search_collection_decorator.rb | 3 +- .../search_collection_decorator_spec.rb | 54 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb index ed70e2b80..1f894f18a 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb @@ -124,8 +124,7 @@ def get_fields(extended) ) end - to_one_relations = %w[ManyToOne OneToOne] - next unless extended && to_one_relations.include?(field.type) + next unless extended && TO_ONE_RELATIONS.include?(field.type) related = @child_collection.datasource.get_collection(field.foreign_collection) diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator_spec.rb index 24a926505..46fb7afcd 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator_spec.rb @@ -137,7 +137,7 @@ module Search context 'when refine_filter' do context 'when the collection has polymorphic relation' do - it 'not search over polymorphic relations with the search extended and show a debug log' do + it 'not search over PolymorphicManyToOne relations with the search extended and show a debug log' do logger = instance_double(ForestAdminAgent::Services::LoggerService, log: nil) allow(ForestAdminAgent::Facades::Container).to receive(:logger).and_return(logger) filter = Filter.new(search: 'a search value', search_extended: true) @@ -152,6 +152,58 @@ module Search ) end end + + it 'not search over PolymorphicOneToOne relations with the search extended and show a debug log' do + collection_order = ForestAdminDatasourceToolkit::Collection.new(datasource, 'order') + collection_order.add_fields( + { + 'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true, + filter_operators: [Operators::EQUAL]), + 'reference' => ColumnSchema.new(column_type: 'String', + filter_operators: [Operators::I_CONTAINS]), + 'document_attachment' => Relations::PolymorphicOneToOneSchema.new( + origin_key: 'record_id', + origin_key_target: 'id', + foreign_collection: 'attachment', + origin_type_field: 'record_type', + origin_type_value: 'Order' + ) + } + ) + + collection_attachment = ForestAdminDatasourceToolkit::Collection.new(datasource, 'attachment') + collection_attachment.add_fields( + { + 'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true, + filter_operators: [Operators::EQUAL]), + 'name' => ColumnSchema.new(column_type: 'String', + filter_operators: [Operators::I_CONTAINS]) + } + ) + + datasource.add_collection(collection_order) + datasource.add_collection(collection_attachment) + + logger = instance_double(ForestAdminAgent::Services::LoggerService, log: nil) + allow(ForestAdminAgent::Facades::Container).to receive(:logger).and_return(logger) + + filter = Filter.new(search: 'test', search_extended: true) + search_collection_decorator = described_class.new(collection_order, datasource) + refined_filter = search_collection_decorator.refine_filter(caller, filter) + + # Should log the debug message for PolymorphicOneToOne + expect(ForestAdminAgent::Facades::Container.logger).to have_received(:log).with( + 'Debug', + "We're not searching through order.document_attachment because it's a polymorphic relation. " \ + "You can override the default search behavior with 'replace_search'. " \ + 'See more: https://docs.forestadmin.com/developer-guide-agents-ruby/agent-customization/search' + ) + + # Should only search on direct fields, not on the polymorphic relation fields + expect(refined_filter.condition_tree).to have_attributes( + field: 'reference', operator: Operators::I_CONTAINS, value: 'test' + ) + end end context 'when the search value is null' do