From 62696b9ee7c01998597159eb19ac0c094ca1b207 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Wed, 5 Apr 2023 07:16:37 +0800 Subject: [PATCH] DEV: Properly support composing multiple category filters on `/filter` (#20953) Before this commit, composing multiple category filters with a query such as category:category1 and category:category2 would not return any results. This is because we were filtering for topics that belonged to both category1 and category2, which is impossible since a topic can only belong to a single category. With this commit, specifying a query like category:category1 category:category2 will now translate to filtering for topics that belong to either the category1 or category2 category. --- lib/topics_filter.rb | 12 ++++++++++-- spec/lib/topics_filter_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/topics_filter.rb b/lib/topics_filter.rb index 20ec17ef0c9..84cd147aacf 100644 --- a/lib/topics_filter.rb +++ b/lib/topics_filter.rb @@ -8,6 +8,7 @@ class TopicsFilter def filter_from_query_string(query_string) return @scope if query_string.blank? + category_or_clause = false query_string.scan( /(?[-=])?(?\w+):(?[^\s]+)/, @@ -45,7 +46,10 @@ class TopicsFilter filter_categories( category_slugs: category_slugs.split(delimiter), exclude_subcategories: key_prefix.presence, + or_clause: category_or_clause, ) + + category_or_clause = true end end end @@ -80,7 +84,7 @@ class TopicsFilter private - def filter_categories(category_slugs:, exclude_subcategories: false) + def filter_categories(category_slugs:, exclude_subcategories: false, or_clause: false) category_ids = Category.ids_from_slugs(category_slugs) category_ids = @@ -96,7 +100,11 @@ class TopicsFilter category_ids = category_ids.flat_map { |category_id| Category.subcategory_ids(category_id) } end - @scope = @scope.joins(:category).where("categories.id IN (?)", category_ids) + if or_clause + @scope.or(Topic.where("categories.id IN (?)", category_ids)) + else + @scope.joins(:category).where("categories.id IN (?)", category_ids) + end end def filter_tags(tag_names:, match_all: true, exclude: false) diff --git a/spec/lib/topics_filter_spec.rb b/spec/lib/topics_filter_spec.rb index 80ae4b0a304..fa0c55f9db2 100644 --- a/spec/lib/topics_filter_spec.rb +++ b/spec/lib/topics_filter_spec.rb @@ -138,6 +138,37 @@ RSpec.describe TopicsFilter do end end + describe "when query string is `category:category category:category2`" do + it "should return topics from any of the specified categories and its subcategories" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("category:category category:category2") + .pluck(:id), + ).to contain_exactly( + topic_in_category.id, + topic_in_category_subcategory.id, + topic_in_category2.id, + topic_in_category2_subcategory.id, + ) + end + end + + describe "when query string is `category:category =category:category2`" do + it "should return topics and subcategory topics from category but only topics from category2" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("category:category =category:category2") + .pluck(:id), + ).to contain_exactly( + topic_in_category.id, + topic_in_category_subcategory.id, + topic_in_category2.id, + ) + end + end + describe "when query string is `=category:category`" do it "should not return topics from subcategories`" do expect(