From 13f1f90c675ea96a172d96d1acafc3240d58962b Mon Sep 17 00:00:00 2001 From: Sam <sam.saffron@gmail.com> Date: Tue, 23 Jun 2015 12:14:06 +1000 Subject: [PATCH] REFACTOR: search improved so filters are extensible - added posts_count filter - fixed it so you can search with a filter only --- lib/search.rb | 202 +++++++++++++++++---------------- spec/components/search_spec.rb | 2 + 2 files changed, 107 insertions(+), 97 deletions(-) diff --git a/lib/search.rb b/lib/search.rb index ad622435c..f014a8d3f 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -125,7 +125,9 @@ class Search # Query a term def execute - return nil if @term.blank? || @term.length < (@opts[:min_search_term_length] || SiteSetting.min_search_term_length) + if @term.blank? || @term.length < (@opts[:min_search_term_length] || SiteSetting.min_search_term_length) + return nil unless @filters.present? + end # If the term is a number or url to a topic, just include that topic if @opts[:search_for_id] && @results.type_filter == 'topic' @@ -145,58 +147,105 @@ class Search @results end + def self.advanced_filter(trigger,&block) + (@advanced_filters ||= {})[trigger] = block + end + + def self.advanced_filters + @advanced_filters + end + + advanced_filter(/status:open/) do |posts| + posts.where('NOT topics.closed AND NOT topics.archived') + end + + advanced_filter(/status:closed/) do |posts| + posts.where('topics.closed') + end + + advanced_filter(/status:archived/) do |posts| + posts.where('topics.archived') + end + + advanced_filter(/status:noreplies/) do |posts| + posts.where("topics.posts_count = 1") + end + + advanced_filter(/status:single_user/) do |posts| + posts.where("topics.participant_count = 1") + end + + advanced_filter(/posts_count:(\d+)/) do |posts, match| + posts.where("topics.posts_count = ?", match.to_i) + end + + + advanced_filter(/in:(likes|bookmarks)/) do |posts, match| + if @guardian.user + post_action_type = PostActionType.types[:like] if match == "likes" + post_action_type = PostActionType.types[:bookmark] if match == "bookmarks" + + posts.where("posts.id IN ( + SELECT pa.post_id FROM post_actions pa + WHERE pa.user_id = #{@guardian.user.id} AND + pa.post_action_type_id = #{post_action_type} + )") + end + end + + advanced_filter(/in:posted/) do |posts| + posts.where("posts.user_id = #{@guardian.user.id}") if @guardian.user + end + + advanced_filter(/in:(watching|tracking)/) do |posts,match| + if @guardian.user + level = TopicUser.notification_levels[match.to_sym] + posts.where("posts.topic_id IN ( + SELECT tu.topic_id FROM topic_users tu + WHERE tu.user_id = #{@guardian.user.id} AND + tu.notification_level >= #{level} + )") + + end + end + + advanced_filter(/category:(.+)/) do |posts,match| + category_id = Category.find_by('name ilike ?', match).try(:id) + posts.where("topics.category_id = ?", category_id) + end + + advanced_filter(/user:(.+)/) do |posts,match| + user_id = User.find_by('username_lower = ?', match.downcase).try(:id) + posts.where("posts.user_id = #{user_id}") + end + private + def process_advanced_search!(term) term.to_s.split(/\s+/).map do |word| - if word == 'status:open' - @status = :open - nil - elsif word == 'status:closed' - @status = :closed - nil - elsif word == 'status:archived' - @status = :archived - nil - elsif word == 'status:noreplies' - @posts_count = 1 - nil - elsif word == 'status:singleuser' - @single_user = true - nil - elsif word == 'order:latest' + + found = false + + Search.advanced_filters.each do |matcher, block| + if word =~ matcher + (@filters ||= []) << [block, $1] + found = true + end + end + + if word == 'order:latest' @order = :latest nil elsif word == 'order:views' @order = :views nil - elsif word =~ /category:(.+)/ - @category_id = Category.find_by('name ilike ?', $1).try(:id) - nil - elsif word =~ /user:(.+)/ - @user_id = User.find_by('username_lower = ?', $1.downcase).try(:id) - nil - elsif word == 'in:likes' - @liked_only = true - nil - elsif word == 'in:posted' - @posted_only = true - nil - elsif word == 'in:watching' - @notification_level = TopicUser.notification_levels[:watching] - nil - elsif word == 'in:tracking' - @notification_level = TopicUser.notification_levels[:tracking] - nil elsif word == 'in:private' @search_pms = true nil - elsif word == 'in:bookmarks' - @bookmarked_only = true - nil else - word + found ? nil : word end end.compact.join(' ') end @@ -210,8 +259,8 @@ class Search else @limit = Search.per_facet + 1 unless @search_context - user_search - category_search + user_search if @term.present? + category_search if @term.present? end topic_search end @@ -306,60 +355,23 @@ class Search posts = posts.where("topics.archetype <> ?", Archetype.private_message) end - if is_topic_search - posts = posts.joins('JOIN users u ON u.id = posts.user_id') - posts = posts.where("posts.raw || ' ' || u.username || ' ' || u.name ilike ?", "%#{@term}%") - else - posts = posts.where("post_search_data.search_data @@ #{ts_query}") - end - - if @status == :open - posts = posts.where('NOT topics.closed AND NOT topics.archived') - elsif @status == :archived - posts = posts.where('topics.archived') - elsif @status == :closed - posts = posts.where('topics.closed') - end - - if @single_user - posts = posts.where("topics.featured_user1_id IS NULL AND topics.last_post_user_id = topics.user_id") - end - - if @posts_count - posts = posts.where("topics.posts_count = #{@posts_count}") - end - - if @user_id - posts = posts.where("posts.user_id = #{@user_id}") - end - - if @guardian.user - if @liked_only || @bookmarked_only - - post_action_type = PostActionType.types[:like] if @liked_only - post_action_type = PostActionType.types[:bookmark] if @bookmarked_only - - posts = posts.where("posts.id IN ( - SELECT pa.post_id FROM post_actions pa - WHERE pa.user_id = #{@guardian.user.id} AND - pa.post_action_type_id = #{post_action_type} - )") + if @term.present? + if is_topic_search + posts = posts.joins('JOIN users u ON u.id = posts.user_id') + posts = posts.where("posts.raw || ' ' || u.username || ' ' || u.name ilike ?", "%#{@term}%") + else + posts = posts.where("post_search_data.search_data @@ #{ts_query}") end - - if @posted_only - posts = posts.where("posts.user_id = #{@guardian.user.id}") - end - - if @notification_level - posts = posts.where("posts.topic_id IN ( - SELECT tu.topic_id FROM topic_users tu - WHERE tu.user_id = #{@guardian.user.id} AND - tu.notification_level >= #{@notification_level} - )") - end - end + @filters.each do |block, match| + if block.arity == 1 + posts = instance_exec(posts, &block) || posts + else + posts = instance_exec(posts, match, &block) || posts + end + end if @filters + # If we have a search context, prioritize those posts first if @search_context.present? @@ -380,11 +392,7 @@ class Search end - if @category_id - posts = posts.where("topics.category_id = ?", @category_id) - end - - if @order == :latest + if @order == :latest || @term.blank? if opts[:aggregate_search] posts = posts.order("MAX(posts.created_at) DESC") else diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 06bb8ce89..83798b4bc 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -378,11 +378,13 @@ describe Search do expect(Search.execute('test status:closed').posts.length).to eq(0) expect(Search.execute('test status:open').posts.length).to eq(1) + expect(Search.execute('test posts_count:1').posts.length).to eq(1) topic.closed = true topic.save expect(Search.execute('test status:closed').posts.length).to eq(1) + expect(Search.execute('status:closed').posts.length).to eq(1) expect(Search.execute('test status:open').posts.length).to eq(0) topic.archived = true