vrfs-988: refactoring search as text by entity, rather than site-wide

This commit is contained in:
Jonathan Kolyer 2014-01-11 06:26:40 -06:00
parent 43572c1524
commit 3d75f8dd97
6 changed files with 130 additions and 195 deletions

View File

@ -111,23 +111,6 @@ module JamRuby
return recordings
end
def self.search(query, options = { :limit => 10 })
# only issue search if at least 2 characters are specified
if query.nil? || query.length < 2
return []
end
# create 'anded' statement
query = Search.create_tsquery(query)
if query.nil? || query.length == 0
return []
end
return Band.where("name_tsv @@ to_tsquery('jamenglish', ?)", query).limit(options[:limit])
end
# helper method for creating / updating a Band
def self.save(id, name, website, biography, city, state, country, genres, user_id, photo_url, logo_url)
user = User.find(user_id)

View File

@ -1,73 +1,51 @@
module JamRuby
# not a active_record model; just a search result
# not a active_record model; just a search result container
class Search
attr_accessor :bands, :musicians, :fans, :recordings, :friends, :search_type
attr_accessor :bands_filter, :musicians_filter
attr_accessor :filter_results, :text_results
LIMIT = 10
# performs a site-white search
def self.search(query, user_id = nil)
SEARCH_TEXT_TYPES = [:musicians, :bands, :fans]
SEARCH_TEXT_TYPE_ID = :search_text_type
users = User.search(query, :limit => LIMIT)
bands = Band.search(query, :limit => LIMIT)
# NOTE: I removed recordings from search here. This is because we switched
# to "claimed_recordings" so it's not clear what should be searched.
friends = Friendship.search(query, user_id, :limit => LIMIT)
return Search.new(users + bands + friends)
def self.band_search(txt, user = nil)
self.text_search({ SEARCH_TEXT_TYPE_ID => :bands, :query => txt }, user)
end
# performs a friend search scoped to a specific user
# def self.search_by_user(query, user_id)
# friends = Friendship.search(query, user_id, :limit => LIMIT)
# return Search.new(friends)
# end
def self.fan_search(txt, user = nil)
self.text_search({ SEARCH_TEXT_TYPE_ID => :fans, :query => txt }, user)
end
def self.musicians_search(txt, user = nil)
self.text_search({ SEARCH_TEXT_TYPE_ID => :musicians, :query => txt }, user)
end
def self.text_search(params, user = nil)
return [] if params.blank? || params[:query].blank? || 2 >= params[:query].length
tsquery = Search.create_tsquery(params[:query])
return [] if tsquery.blank?
rel = case params[SEARCH_TEXT_TYPE_ID] || SEARCH_TEXT_TYPES[0].to_s
when 'bands'
Band.where("name_tsv @@ to_tsquery('jamenglish', ?)", tsquery)
when 'fans'
User.fans.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
else
User.musicians.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
@text_results = rel.limit(10)
end
# search_results - results from a Tire search across band/user/recording
def initialize(search_results=nil)
@bands = []
@musicians = []
@fans = []
@recordings = []
@friends = []
@musicians_filter = []
@bands_filter = []
if search_results.nil?
return
end
search_results.take(LIMIT).each do |result|
if result.class == User
if result.musician
@musicians.push(result)
else
@fans.push(result)
end
elsif result.class == Band
@bands.push(result)
elsif result.class == Recording
@recordings.push(result)
elsif result.class == Friendship
@friends.push(result.friend)
else
raise Exception, "unknown class #{result.class} returned in search results"
end
end
@text_results, @filter_results = [], []
self
end
def self.create_tsquery(query)
# empty queries don't hit back to elasticsearch
if query.nil? || query.length == 0
return nil
end
return nil if query.nil? || query.length == 0
search_terms = query.split
if search_terms.length == 0
return nil
end
return nil if search_terms.length == 0
args = nil
search_terms.each do |search_term|
@ -76,11 +54,9 @@ module JamRuby
else
args = args + " & " + search_term
end
end
args = args + ":*"
return args
args
end
attr_accessor :user_counters, :page_num, :page_count
@ -105,7 +81,7 @@ module JamRuby
ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering }
end
def self.musician_search(params={}, current_user=nil)
def self.musician_filter(params={}, current_user=nil)
rel = User.musicians
unless (instrument = params[:instrument]).blank?
rel = rel.joins("RIGHT JOIN musicians_instruments AS minst ON minst.user_id = users.id")
@ -160,10 +136,10 @@ module JamRuby
COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION]
def musician_results_for_user(results, user)
@search_type, @musicians_filter = PARAM_MUSICIAN, results
@filter_results = results
if user
@user_counters = results.inject({}) { |hh,val| hh[val.id] = []; hh }
mids = "'#{@musicians_filter.map(&:id).join("','")}'"
mids = "'#{@filter_results.map(&:id).join("','")}'"
# this gets counts for each search result on friends/follows/records/sessions
results.each do |uu|
@ -247,7 +223,7 @@ module JamRuby
end
end
def self.band_search(params={}, current_user=nil)
def self.band_filter(params={}, current_user=nil)
rel = Band.scoped
unless (genre = params[:genre]).blank?
@ -287,10 +263,10 @@ module JamRuby
end
def band_results_for_user(results, user)
@search_type, @bands_filter = PARAM_BAND, results
@filter_results = results
if user
@user_counters = results.inject({}) { |hh,val| hh[val.id] = []; hh }
mids = "'#{@bands_filter.map(&:id).join("','")}'"
mids = "'#{@filter_results.map(&:id).join("','")}'"
# this gets counts for each search result
results.each do |bb|

View File

@ -140,6 +140,7 @@ module JamRuby
validate :update_email_case_insensitive_uniqueness, :if => :updating_email
scope :musicians, where(:musician => true)
scope :fans, where(:musician => false)
scope :geocoded_users, where(['lat IS NOT NULL AND lng IS NOT NULL'])
scope :musicians_geocoded, musicians.geocoded_users
@ -924,31 +925,6 @@ module JamRuby
end
end
def self.search(query, options = { :limit => 10 })
# only issue search if at least 2 characters are specified
if query.nil? || query.length < 2
return []
end
# save query for use in instrument search
search_criteria = query
# create 'anded' statement
query = Search.create_tsquery(query)
if query.nil? || query.length == 0
return []
end
# remove email_confirmed restriction due to VRFS-378
# .where("email_confirmed = true AND (name_tsv @@ to_tsquery('jamenglish', ?) OR users.id in (select user_id from musicians_instruments where instrument_id like '%#{search_criteria.downcase}%'))", query)
return query = User
.where("(name_tsv @@ to_tsquery('jamenglish', ?) OR users.id in (select user_id from musicians_instruments where instrument_id like '%#{search_criteria.downcase}%'))", query)
.limit(options[:limit])
end
def provides_location?
!self.city.blank? && (!self.state.blank? || !self.country.blank?)
end

View File

@ -25,15 +25,15 @@ describe 'Band search' do
it "finds all bands" do
# expects all the bands
num = Band.count
results = Search.band_search({ :per_page => num })
expect(results.bands_filter.count).to eq(num)
results = Search.band_filter({ :per_page => num })
expect(results.filter_results.count).to eq(num)
end
it "finds bands with proper ordering" do
# the ordering should be create_at since no followers exist
expect(BandFollower.count).to eq(0)
results = Search.band_search({ :per_page => Band.count })
results.bands_filter.each_with_index do |uu, idx|
results = Search.band_filter({ :per_page => Band.count })
results.filter_results.each_with_index do |uu, idx|
expect(uu.id).to eq(@bands.reverse[idx].id)
end
end
@ -53,11 +53,11 @@ describe 'Band search' do
# refresh the order to ensure it works right
@band2.followers.concat(users[1..-1])
results = Search.band_search({ :per_page => @bands.size }, users[0])
expect(results.bands_filter[0].id).to eq(@band2.id)
results = Search.band_filter({ :per_page => @bands.size }, users[0])
expect(results.filter_results[0].id).to eq(@band2.id)
# check the follower count for given entry
expect(results.bands_filter[0].search_follow_count.to_i).not_to eq(0)
expect(results.filter_results[0].search_follow_count.to_i).not_to eq(0)
# check the follow relationship between current_user and result
expect(results.is_follower?(@band2)).to be true
end
@ -65,8 +65,8 @@ describe 'Band search' do
it 'paginates properly' do
# make sure pagination works right
params = { :per_page => 2, :page => 1 }
results = Search.band_search(params)
expect(results.bands_filter.count).to be 2
results = Search.band_filter(params)
expect(results.filter_results.count).to be 2
end
end
@ -87,8 +87,8 @@ describe 'Band search' do
# establish sorting order
@band1.followers.concat(users)
results = Search.band_search({},@band1)
uu = results.bands_filter.detect { |mm| mm.id == @band1.id }
results = Search.band_filter({},@band1)
uu = results.filter_results.detect { |mm| mm.id == @band1.id }
expect(uu).to_not be_nil
expect(results.follow_count(uu)).to eq(users.count)
end
@ -96,8 +96,8 @@ describe 'Band search' do
it "session stat shows session count" do
make_session(@band1)
@band1.reload
results = Search.band_search({},@band1)
uu = results.bands_filter.detect { |mm| mm.id == @band1.id }
results = Search.band_filter({},@band1)
uu = results.filter_results.detect { |mm| mm.id == @band1.id }
expect(uu).to_not be_nil
expect(results.session_count(uu)).to be 1
end
@ -112,9 +112,9 @@ describe 'Band search' do
make_session(@band2)
make_session(@band1)
# order results by num recordings
results = Search.band_search({ :orderby => 'plays' })
expect(results.bands_filter[0].id).to eq(@band2.id)
expect(results.bands_filter[1].id).to eq(@band1.id)
results = Search.band_filter({ :orderby => 'plays' })
expect(results.filter_results[0].id).to eq(@band2.id)
expect(results.filter_results[1].id).to eq(@band1.id)
end
it "by now playing" do
@ -122,18 +122,18 @@ describe 'Band search' do
session = make_session(@band3)
FactoryGirl.create(:music_session_history, :music_session => session)
results = Search.band_search({ :orderby => 'playing' })
expect(results.bands_filter.count).to be 1
expect(results.bands_filter.first.id).to eq(@band3.id)
results = Search.band_filter({ :orderby => 'playing' })
expect(results.filter_results.count).to be 1
expect(results.filter_results.first.id).to eq(@band3.id)
# should get 2 results with 2 active sessions
# sort order should be created_at DESC
session = make_session(@band4)
FactoryGirl.create(:music_session_history, :music_session => session)
results = Search.band_search({ :orderby => 'playing' })
expect(results.bands_filter.count).to be 2
expect(results.bands_filter[0].id).to eq(@band4.id)
expect(results.bands_filter[1].id).to eq(@band3.id)
results = Search.band_filter({ :orderby => 'playing' })
expect(results.filter_results.count).to be 2
expect(results.filter_results[0].id).to eq(@band4.id)
expect(results.filter_results[1].id).to eq(@band3.id)
end
end
@ -146,40 +146,40 @@ describe 'Band search' do
@band1.reload
ggg = @band1.genres.detect { |gg| gg.id == genre.id }
expect(ggg).to_not be_nil
results = Search.band_search({ :genre => ggg.id })
results.bands_filter.each do |rr|
results = Search.band_filter({ :genre => ggg.id })
results.filter_results.each do |rr|
expect(rr.genres.detect { |gg| gg.id==ggg.id }.id).to eq(genre.id)
end
expect(results.bands_filter.count).to be 1
expect(results.filter_results.count).to be 1
end
it "finds bands within a given distance of given location" do
num = Band.count
expect(@band1.lat).to_not be_nil
# short distance
results = Search.band_search({ :per_page => num,
results = Search.band_filter({ :per_page => num,
:distance => 10,
:city => 'Apex' }, @band1)
expect(results.bands_filter.count).to be num
expect(results.filter_results.count).to be num
# long distance
results = Search.band_search({ :per_page => num,
results = Search.band_filter({ :per_page => num,
:distance => 1000,
:city => 'Miami',
:state => 'FL' }, @band1)
expect(results.bands_filter.count).to be num
expect(results.filter_results.count).to be num
end
it "finds bands within a given distance of bands location" do
expect(@band1.lat).to_not be_nil
# uses the location of @band1
results = Search.band_search({ :distance => 10, :per_page => Band.count }, @band1)
expect(results.bands_filter.count).to be Band.count
results = Search.band_filter({ :distance => 10, :per_page => Band.count }, @band1)
expect(results.filter_results.count).to be Band.count
end
it "finds no bands within a given distance of location" do
expect(@band1.lat).to_not be_nil
results = Search.band_search({ :distance => 10, :city => 'San Francisco' }, @band1)
expect(results.bands_filter.count).to be 0
results = Search.band_filter({ :distance => 10, :city => 'San Francisco' }, @band1)
expect(results.filter_results.count).to be 0
end
end

View File

@ -13,7 +13,7 @@ describe User do
end
it "should allow search of one band with an exact match" do
ws = Band.search("Example Band")
ws = Search.band_search("Example Band")
ws.length.should == 1
band_result = ws[0]
band_result.name.should == @band.name
@ -22,61 +22,61 @@ describe User do
end
it "should allow search of one band with partial matches" do
ws = Band.search("Ex")
ws = Search.band_search("Ex")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Exa")
ws = Search.band_search("Exa")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Exam")
ws = Search.band_search("Exam")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Examp")
ws = Search.band_search("Examp")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Exampl")
ws = Search.band_search("Exampl")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Example")
ws = Search.band_search("Example")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Ba")
ws = Search.band_search("Ba")
ws.length.should == 1
ws[0].id.should == @band.id
ws = Band.search("Ban")
ws = Search.band_search("Ban")
ws.length.should == 1
ws[0].id.should == @band.id
end
it "should not match mid-word searchs" do
ws = Band.search("xa")
ws = Search.band_search("xa")
ws.length.should == 0
ws = Band.search("le")
ws = Search.band_search("le")
ws.length.should == 0
end
it "should delete band" do
ws = Band.search("Example Band")
ws = Search.band_search("Example Band")
ws.length.should == 1
band_result = ws[0]
band_result.id.should == @band.id
@band.destroy # delete doesn't work; you have to use destroy.
ws = Band.search("Example Band")
ws = Search.band_search("Example Band")
ws.length.should == 0
end
it "should update band" do
ws = Band.search("Example Band")
ws = Search.band_search("Example Band")
ws.length.should == 1
band_result = ws[0]
band_result.id.should == @band.id
@ -84,10 +84,10 @@ describe User do
@band.name = "bonus-stuff"
@band.save
ws = Band.search("Example Band")
ws = Search.band_search("Example Band")
ws.length.should == 0
ws = Band.search("Bonus")
ws = Search.band_search("Bonus")
ws.length.should == 1
band_result = ws[0]
band_result.id.should == @band.id
@ -96,7 +96,7 @@ describe User do
it "should tokenize correctly" do
@band2 = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
ws = Band.search("pea")
ws = Search.band_search("pea")
ws.length.should == 1
user_result = ws[0]
user_result.id.should == @band2.id
@ -105,12 +105,12 @@ describe User do
it "should not return anything with a 1 character search" do
@band2 = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
ws = Band.search("pe")
ws = Search.band_search("pe")
ws.length.should == 1
user_result = ws[0]
user_result.id.should == @band2.id
ws = Band.search("p")
ws = Search.band_search("p")
ws.length.should == 0
end
end

View File

@ -17,15 +17,15 @@ describe 'Musician search' do
it "finds all musicians" do
# expects all the users
num = User.musicians.count
results = Search.musician_search({ :per_page => num })
expect(results.musicians_filter.count).to eq(num)
results = Search.musician_filter({ :per_page => num })
expect(results.filter_results.count).to eq(num)
end
it "finds musicians with proper ordering" do
# the ordering should be create_at since no followers exist
expect(UserFollower.count).to eq(0)
results = Search.musician_search({ :per_page => User.musicians.count })
results.musicians_filter.each_with_index do |uu, idx|
results = Search.musician_filter({ :per_page => User.musicians.count })
results.filter_results.each_with_index do |uu, idx|
expect(uu.id).to eq(@users.reverse[idx].id)
end
end
@ -40,11 +40,11 @@ describe 'Musician search' do
# refresh the order to ensure it works right
@user2.followers.concat([@user3, @user4, @user2])
results = Search.musician_search({ :per_page => @users.size }, @user3)
expect(results.musicians_filter[0].id).to eq(@user2.id)
results = Search.musician_filter({ :per_page => @users.size }, @user3)
expect(results.filter_results[0].id).to eq(@user2.id)
# check the follower count for given entry
expect(results.musicians_filter[0].search_follow_count.to_i).not_to eq(0)
expect(results.filter_results[0].search_follow_count.to_i).not_to eq(0)
# check the follow relationship between current_user and result
expect(results.is_follower?(@user2)).to be true
end
@ -52,8 +52,8 @@ describe 'Musician search' do
it 'paginates properly' do
# make sure pagination works right
params = { :per_page => 2, :page => 1 }
results = Search.musician_search(params)
expect(results.musicians_filter.count).to be 2
results = Search.musician_filter(params)
expect(results.filter_results.count).to be 2
end
end
@ -95,8 +95,8 @@ describe 'Musician search' do
# create friendship record
Friendship.save(@user1.id, @user2.id)
# search on user2
results = Search.musician_search({}, @user2)
friend = results.musicians_filter.detect { |mm| mm.id == @user1.id }
results = Search.musician_filter({}, @user2)
friend = results.filter_results.detect { |mm| mm.id == @user1.id }
expect(friend).to_not be_nil
expect(results.friend_count(friend)).to be 1
@user1.reload
@ -114,8 +114,8 @@ describe 'Musician search' do
expect(recording.claimed_recordings.length).to be 1
expect(@user1.recordings.detect { |rr| rr == recording }).to_not be_nil
results = Search.musician_search({},@user1)
uu = results.musicians_filter.detect { |mm| mm.id == @user1.id }
results = Search.musician_filter({},@user1)
uu = results.filter_results.detect { |mm| mm.id == @user1.id }
expect(uu).to_not be_nil
expect(results.record_count(uu)).to be 1
@ -129,29 +129,29 @@ describe 'Musician search' do
it "by plays" do
make_recording(@user1)
# order results by num recordings
results = Search.musician_search({ :orderby => 'plays' }, @user2)
expect(results.musicians_filter[0].id).to eq(@user1.id)
results = Search.musician_filter({ :orderby => 'plays' }, @user2)
expect(results.filter_results[0].id).to eq(@user1.id)
# add more data and make sure order still correct
make_recording(@user2); make_recording(@user2)
results = Search.musician_search({ :orderby => 'plays' }, @user2)
expect(results.musicians_filter[0].id).to eq(@user2.id)
results = Search.musician_filter({ :orderby => 'plays' }, @user2)
expect(results.filter_results[0].id).to eq(@user2.id)
end
it "by now playing" do
# should get 1 result with 1 active session
make_session(@user3)
results = Search.musician_search({ :orderby => 'playing' }, @user2)
expect(results.musicians_filter.count).to be 1
expect(results.musicians_filter.first.id).to eq(@user3.id)
results = Search.musician_filter({ :orderby => 'playing' }, @user2)
expect(results.filter_results.count).to be 1
expect(results.filter_results.first.id).to eq(@user3.id)
# should get 2 results with 2 active sessions
# sort order should be created_at DESC
make_session(@user4)
results = Search.musician_search({ :orderby => 'playing' }, @user2)
expect(results.musicians_filter.count).to be 2
expect(results.musicians_filter[0].id).to eq(@user4.id)
expect(results.musicians_filter[1].id).to eq(@user3.id)
results = Search.musician_filter({ :orderby => 'playing' }, @user2)
expect(results.filter_results.count).to be 2
expect(results.filter_results[0].id).to eq(@user4.id)
expect(results.filter_results[1].id).to eq(@user3.id)
end
end
@ -165,40 +165,40 @@ describe 'Musician search' do
@user1.reload
ii = @user1.instruments.detect { |inst| inst.id == 'tuba' }
expect(ii).to_not be_nil
results = Search.musician_search({ :instrument => ii.id })
results.musicians_filter.each do |rr|
results = Search.musician_filter({ :instrument => ii.id })
results.filter_results.each do |rr|
expect(rr.instruments.detect { |inst| inst.id=='tuba' }.id).to eq(ii.id)
end
expect(results.musicians_filter.count).to be 1
expect(results.filter_results.count).to be 1
end
it "finds musicians within a given distance of given location" do
num = User.musicians.count
expect(@user1.lat).to_not be_nil
# short distance
results = Search.musician_search({ :per_page => num,
results = Search.musician_filter({ :per_page => num,
:distance => 10,
:city => 'Apex' }, @user1)
expect(results.musicians_filter.count).to be num
expect(results.filter_results.count).to be num
# long distance
results = Search.musician_search({ :per_page => num,
results = Search.musician_filter({ :per_page => num,
:distance => 1000,
:city => 'Miami',
:state => 'FL' }, @user1)
expect(results.musicians_filter.count).to be num
expect(results.filter_results.count).to be num
end
it "finds musicians within a given distance of users location" do
expect(@user1.lat).to_not be_nil
# uses the location of @user1
results = Search.musician_search({ :distance => 10, :per_page => User.musicians.count }, @user1)
expect(results.musicians_filter.count).to be User.musicians.count
results = Search.musician_filter({ :distance => 10, :per_page => User.musicians.count }, @user1)
expect(results.filter_results.count).to be User.musicians.count
end
it "finds no musicians within a given distance of location" do
expect(@user1.lat).to_not be_nil
results = Search.musician_search({ :distance => 10, :city => 'San Francisco' }, @user1)
expect(results.musicians_filter.count).to be 0
results = Search.musician_filter({ :distance => 10, :city => 'San Francisco' }, @user1)
expect(results.filter_results.count).to be 0
end
end