add tag scoped search
This commit is contained in:
parent
bb65fddd2a
commit
d64700a9c1
|
@ -53,15 +53,26 @@ class Search
|
|||
end
|
||||
end
|
||||
|
||||
def with_tags(base, tag_scopes)
|
||||
base
|
||||
.joins({ :taggings => :tag }, :user)
|
||||
.where(:tags => { :tag => tag_scopes })
|
||||
.having("COUNT(stories.id) = ?", tag_scopes.length)
|
||||
.group("stories.id")
|
||||
end
|
||||
|
||||
def search_for_user!(user)
|
||||
self.results = []
|
||||
self.total_results = 0
|
||||
|
||||
# extract domain query since it must be done separately
|
||||
domain = nil
|
||||
tag_scopes = []
|
||||
words = self.q.to_s.split(" ").reject{|w|
|
||||
if m = w.match(/^domain:(.+)$/)
|
||||
domain = m[1]
|
||||
elsif m = w.match(/^tag:(.+)$/)
|
||||
tag_scopes << m[1]
|
||||
end
|
||||
}.join(" ")
|
||||
|
||||
|
@ -71,9 +82,7 @@ class Search
|
|||
|
||||
case self.what
|
||||
when "stories"
|
||||
base = Story.unmerged.where(:is_expired => false).
|
||||
includes({ :taggings => :tag }, :user)
|
||||
|
||||
base = Story.unmerged.where(:is_expired => false)
|
||||
if domain.present?
|
||||
begin
|
||||
reg = Regexp.new("//([^/]*\.)?#{domain}/")
|
||||
|
@ -84,36 +93,49 @@ class Search
|
|||
end
|
||||
end
|
||||
|
||||
title_match_sql = "MATCH(stories.title) AGAINST('#{qwords}' IN BOOLEAN MODE)"
|
||||
description_match_sql = "MATCH(stories.description) AGAINST('#{qwords}' IN BOOLEAN MODE)"
|
||||
story_cache_match_sql = "MATCH(stories.story_cache) AGAINST('#{qwords}' IN BOOLEAN MODE)"
|
||||
|
||||
if qwords.present?
|
||||
base.where!(
|
||||
"(MATCH(title) AGAINST('#{qwords}' IN BOOLEAN MODE) OR " +
|
||||
"MATCH(description) AGAINST('#{qwords}' IN BOOLEAN MODE) OR " +
|
||||
"MATCH(story_cache) AGAINST('#{qwords}' IN BOOLEAN MODE))"
|
||||
"(#{title_match_sql} OR " +
|
||||
"#{description_match_sql} OR " +
|
||||
"#{story_cache_match_sql})"
|
||||
)
|
||||
|
||||
self.results = base.select(
|
||||
"stories.*, " +
|
||||
"MATCH(title) AGAINST('#{qwords}' IN BOOLEAN MODE) AS rel_title, " +
|
||||
"MATCH(description) AGAINST('#{qwords}' IN BOOLEAN MODE) AS rel_description, " +
|
||||
"MATCH(story_cache) AGAINST('#{qwords}' IN BOOLEAN MODE) AS rel_story_cache"
|
||||
)
|
||||
if tag_scopes.present?
|
||||
self.results = with_tags(base, tag_scopes)
|
||||
else
|
||||
base = base.includes({ :taggings => :tag }, :user)
|
||||
self.results = base.select(
|
||||
"stories.*, " +
|
||||
"#{title_match_sql}, " +
|
||||
"#{description_match_sql}, " +
|
||||
"#{story_cache_match_sql}"
|
||||
)
|
||||
end
|
||||
else
|
||||
self.results = base
|
||||
if tag_scopes.present?
|
||||
self.results = with_tags(base, tag_scopes)
|
||||
else
|
||||
self.results = base.includes({ :taggings => :tag }, :user)
|
||||
end
|
||||
end
|
||||
|
||||
case self.order
|
||||
when "relevance"
|
||||
if qwords.present?
|
||||
self.results.order!(
|
||||
"(rel_title * 2) DESC, " +
|
||||
"(rel_description * 1.5) DESC, " +
|
||||
"(rel_story_cache) DESC"
|
||||
"((#{title_match_sql}) * 2) DESC, " +
|
||||
"((#{description_match_sql}) * 1.5) DESC, " +
|
||||
"(#{story_cache_match_sql}) DESC"
|
||||
)
|
||||
else
|
||||
self.results.order!("created_at DESC")
|
||||
self.results.order!("stories.created_at DESC")
|
||||
end
|
||||
when "newest"
|
||||
self.results.order!("created_at DESC")
|
||||
self.results.order!("stories.created_at DESC")
|
||||
when "points"
|
||||
self.results.order!("#{Story.score_sql} DESC")
|
||||
end
|
||||
|
@ -138,7 +160,11 @@ class Search
|
|||
end
|
||||
end
|
||||
|
||||
self.total_results = base.count
|
||||
if tag_scopes.present?
|
||||
self.total_results = self.results.length
|
||||
else
|
||||
self.total_results = base.count
|
||||
end
|
||||
|
||||
if self.page > self.page_count
|
||||
self.page = self.page_count
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe Search do
|
||||
|
||||
# We need to set up and then teardown the environment
|
||||
# outside of the typical RSpec transaction because
|
||||
# the search module uses custom SQL that doesn't
|
||||
# work inside the transaction
|
||||
before(:all) do
|
||||
@user = User.make!
|
||||
|
||||
@multi_tag = Story.make!(:title => "multitag term1 t1 t2",
|
||||
:url => "https://example.com/3",
|
||||
:user_id => @user.id,
|
||||
:tags_a => ["tag1", "tag2"])
|
||||
@stories = [
|
||||
Story.make!(:title => "unique",
|
||||
:url => "https://example.com/unique",
|
||||
:user_id => @user.id,
|
||||
:tags_a => ["tag1"]),
|
||||
Story.make!(:title => "term1 domain1",
|
||||
:url => "https://example.com/1",
|
||||
:user_id => @user.id,
|
||||
:tags_a => ["tag1"]),
|
||||
Story.make!(:title => "term1 t2",
|
||||
:url => "https://example.com/2",
|
||||
:user_id => @user.id,
|
||||
:tags_a => ["tag2"]),
|
||||
@multi_tag,
|
||||
Story.make!(:title => "term1 domain2",
|
||||
:url => "https://lobste.rs/1",
|
||||
:user_id => @user.id,
|
||||
:tags_a => ["tag1"]),
|
||||
]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user.destroy!
|
||||
@stories.each { |s| s.destroy! }
|
||||
end
|
||||
|
||||
it "can search for stories" do
|
||||
search = Search.new
|
||||
search.q = "unique"
|
||||
|
||||
search.search_for_user!(@user)
|
||||
|
||||
expect(search.results.length).to eq(1)
|
||||
expect(search.results.first.title).to eq("unique")
|
||||
end
|
||||
|
||||
it "can search for multitaged stories" do
|
||||
search = Search.new
|
||||
search.q = "multitag"
|
||||
|
||||
search.search_for_user!(@user)
|
||||
|
||||
expect(search.results.length).to eq(1)
|
||||
expect(search.results.first.title).to eq("multitag term1 t1 t2")
|
||||
end
|
||||
|
||||
it "can search for stories by domain" do
|
||||
search = Search.new
|
||||
search.q = "term1 domain:lobste.rs"
|
||||
|
||||
search.search_for_user!(@user)
|
||||
|
||||
expect(search.results.length).to eq(1)
|
||||
expect(search.results.first.title).to eq("term1 domain2")
|
||||
end
|
||||
|
||||
it "can search for stories by tag" do
|
||||
search = Search.new
|
||||
search.q = "term1 tag:tag1"
|
||||
|
||||
search.search_for_user!(@user)
|
||||
|
||||
expect(search.results.length).to eq(3)
|
||||
|
||||
# Stories with multiple tags should return all the tags
|
||||
multi_tag_res = search.results.select do |res|
|
||||
res.id == @multi_tag.id
|
||||
end
|
||||
|
||||
expect(multi_tag_res.length).to eq(1)
|
||||
expect(multi_tag_res.first.sorted_taggings.first.tag.tag).to eq("tag1")
|
||||
expect(multi_tag_res.first.sorted_taggings.second.tag.tag).to eq("tag2")
|
||||
end
|
||||
|
||||
it "should return only stories with both tags if multiple tags are present" do
|
||||
search = Search.new
|
||||
search.q = "term1 tag:tag1 tag:tag2"
|
||||
|
||||
search.search_for_user!(@user)
|
||||
|
||||
expect(search.results.length).to eq(1)
|
||||
end
|
||||
|
||||
it "can search for stories with only tags" do
|
||||
search = Search.new
|
||||
search.q = "tag:tag2"
|
||||
|
||||
search.search_for_user!(@user)
|
||||
|
||||
expect(search.results.length).to eq(2)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue