diff --git a/db/manifest b/db/manifest index 3da5b1ebc..db2adf057 100755 --- a/db/manifest +++ b/db/manifest @@ -175,4 +175,5 @@ audio_latency.sql ams_index.sql update_ams_index.sql update_ams_index_2.sql -sms_index.sql \ No newline at end of file +sms_index.sql +music_sessions_description_search.sql \ No newline at end of file diff --git a/db/up/music_sessions_description_search.sql b/db/up/music_sessions_description_search.sql new file mode 100644 index 000000000..39be0957b --- /dev/null +++ b/db/up/music_sessions_description_search.sql @@ -0,0 +1,9 @@ +-- add full search capabilities for description (threw in genre too) +ALTER TABLE music_sessions ADD COLUMN description_tsv tsvector; +CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE +ON music_sessions FOR EACH ROW EXECUTE PROCEDURE +tsvector_update_trigger(description_tsv, 'public.jamenglish', description); +CREATE INDEX music_sessions_description_tsv_index ON music_sessions USING gin(description_tsv); + +-- prime the pump +UPDATE music_sessions set description = description; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index 66a457ec2..8a5af03e6 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -1,5 +1,8 @@ module JamRuby class ActiveMusicSession < ActiveRecord::Base + + @@log = Logging.logger[ActiveMusicSession] + self.primary_key = 'id' self.table_name = 'active_music_sessions' @@ -337,10 +340,8 @@ module JamRuby keyword = options[:keyword] offset = options[:offset] limit = options[:limit] - - connection = Connection.where(user_id: current_user.id, client_id: client_id).first! - my_locidispid = connection.locidispid - my_audio_latency = connection.last_jam_audio_latency + day = options[:day] + timezone_offset = options[:timezone_offset] query = MusicSession .select('music_sessions.*') @@ -380,29 +381,32 @@ module JamRuby } ) - if offset - query = query.offset(offset) - end + # if not specified, default offset to 0 + offset ||= 0 + offset = offset.to_i + # if not specified, default limit to 20 + limit ||= 20 + limit = limit.to_i - if limit - query = query.limit(limit) - end + query = query.offset(offset) + query = query.limit(limit) + query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank? + query = query.where('music_sessions.language = ?', lang) unless lang.blank? + query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", keyword + ':*') unless keyword.blank? - # cleanse keyword so it is only word characters. ignore if less than 3 characters long or greater than 100 - # TODO do we want to force match of whole words only? this matches any substring... - # TODO do we want to match more than one word in the phrase? this matches only the first word... - - unless keyword.nil? or keyword.length < 3 or keyword.length > 100 - w = keyword.split(/\W+/) - if w.length > 0 and w[0].length >= 3 - query = query.where("music_sessions.description like '%#{w[0]}%'") + if !day.blank? && !timezone_offset.blank? + begin + day = Date.parse(day) + next_day = day + 1 + timezone_offset = timezone_offset.to_i + query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}' + AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'") + rescue Exception => e + # do nothing. bad date probably + @@log.warn("unable to parse day=#{day}, timezone_offset=#{timezone_offset}, e=#{e}") end end - query = query.where("music_sessions.genre_id = ?", genre) unless genre.nil? - - # TODO filter by lang - return query end @@ -428,13 +432,7 @@ module JamRuby def self.ams_index(current_user, params) ActiveMusicSession.ams_init(current_user, params) - music_sessions = ActiveMusicSession.ams_query(current_user, - client_id: params[:client_id], - genre: params[:genre], - lang: params[:lang], - keyword: params[:keyword], - offset: params[:offset], - limit: params[:limit]).all + music_sessions = ActiveMusicSession.ams_query(current_user, params).all music_session_users = ActiveMusicSession.ams_users.all diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index 3cb499eb7..91f71075a 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -3,6 +3,8 @@ require 'iso-639' module JamRuby class MusicSession < ActiveRecord::Base + @@log = Logging.logger[MusicSession] + NO_RECURRING = 'once' RECURRING_WEEKLY = 'weekly' @@ -591,10 +593,8 @@ module JamRuby keyword = options[:keyword] offset = options[:offset] limit = options[:limit] - - connection = Connection.where(user_id: current_user.id, client_id: client_id).first! - my_locidispid = connection.locidispid - my_audio_latency = connection.last_jam_audio_latency + day = options[:day] + timezone_offset = options[:timezone_offset] query = MusicSession .select('music_sessions.*') @@ -634,29 +634,32 @@ module JamRuby } ) - if offset - query = query.offset(offset) - end + # if not specified, default offset to 0 + offset ||= 0 + offset = offset.to_i + # if not specified, default limit to 20 + limit ||= 20 + limit = limit.to_i - if limit - query = query.limit(limit) - end + query = query.offset(offset) + query = query.limit(limit) + query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank? + query = query.where('music_sessions.language = ?', lang) unless lang.blank? + query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", keyword + ':*') unless keyword.blank? - # cleanse keyword so it is only word characters. ignore if less than 3 characters long or greater than 100 - # TODO do we want to force match of whole words only? this matches any substring... - # TODO do we want to match more than one word in the phrase? this matches only the first word... - - unless keyword.nil? or keyword.length < 3 or keyword.length > 100 - w = keyword.split(/\W+/) - if w.length > 0 and w[0].length >= 3 - query = query.where("music_sessions.description like '%#{w[0]}%'") + if !day.blank? && !timezone_offset.blank? + begin + day = Date.parse(day) + next_day = day + 1 + timezone_offset = timezone_offset.to_i + query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}' + AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'") + rescue Exception => e + # do nothing. bad date probably + @@log.warn("unable to parse day=#{day}, timezone_offset=#{timezone_offset}, e=#{e}") end end - query = query.where("music_sessions.genre_id = ?", genre) unless genre.nil? - - # TODO filter by lang - return query end @@ -682,13 +685,7 @@ module JamRuby def self.sms_index(current_user, params) MusicSession.sms_init(current_user, params) - music_sessions = MusicSession.sms_query(current_user, - client_id: params[:client_id], - genre: params[:genre], - lang: params[:lang], - keyword: params[:keyword], - offset: params[:offset], - limit: params[:limit]).all + music_sessions = MusicSession.sms_query(current_user, params).all music_session_users = MusicSession.sms_users.all diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 1e6086be8..6c759c4d8 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -58,13 +58,14 @@ FactoryGirl.define do legal_terms true genre JamRuby::Genre.first band nil + language 'en' end before(:create) do |session, evaluator| music_session = FactoryGirl.create(:music_session, name: evaluator.name, description: evaluator.description, fan_chat: evaluator.fan_chat, fan_access: evaluator.fan_access, approval_required: evaluator.approval_required, musician_access: evaluator.musician_access, - genre: evaluator.genre, creator: evaluator.creator, band: evaluator.band) + genre: evaluator.genre, creator: evaluator.creator, band: evaluator.band, language: evaluator.language) session.id = music_session.id end diff --git a/ruby/spec/jam_ruby/models/active_music_session_spec.rb b/ruby/spec/jam_ruby/models/active_music_session_spec.rb index ff7557828..79a0992d6 100644 --- a/ruby/spec/jam_ruby/models/active_music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/active_music_session_spec.rb @@ -332,6 +332,13 @@ describe ActiveMusicSession do end end + + def ams(user, params) + ActiveRecord::Base.transaction do + return ActiveMusicSession.ams_index(user, params) + end + end + describe "ams_index" do it "does not crash" do @@ -347,8 +354,8 @@ describe ActiveMusicSession do user = FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 5) c3 = FactoryGirl.create(:connection, user: user, locidispid: 1, last_jam_audio_latency: 5) - Score.createx(c1.locidispid, c1.client_id, c1.addr, c3.locidispid, c3.client_id, c3.addr, 20, nil); - Score.createx(c2.locidispid, c2.client_id, c2.addr, c3.locidispid, c3.client_id, c3.addr, 30, nil); + Score.createx(c1.locidispid, c1.client_id, c1.addr, c3.locidispid, c3.client_id, c3.addr, 20, nil) + Score.createx(c2.locidispid, c2.client_id, c2.addr, c3.locidispid, c3.client_id, c3.addr, 30, nil) # make a transaction @@ -384,6 +391,120 @@ describe ActiveMusicSession do end end + describe "parameters" do + let(:creator_1) { FactoryGirl.create(:user, last_jam_locidispid: 4, last_jam_audio_latency: 8) } + let(:creator_conn_1) { FactoryGirl.create(:connection, user: creator_1, ip_address: '4.4.4.4', locidispid: 4, addr:4) } + let(:creator_2) { FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 10) } + let(:creator_conn_2) { FactoryGirl.create(:connection, user: creator_2, ip_address: '4.4.4.4', locidispid: 1, addr:1) } + let(:creator_3) { FactoryGirl.create(:user, last_jam_locidispid: 2, last_jam_audio_latency: 12) } + let(:creator_conn_3) { FactoryGirl.create(:connection, user: creator_3, ip_address: '5.5.5.5', locidispid: 2, addr:2) } + let(:searcher_1) { FactoryGirl.create(:user, last_jam_locidispid: 5, last_jam_audio_latency: 6) } + let(:searcher_conn_1) { FactoryGirl.create(:connection, user: searcher_1, ip_address: '8.8.8.8', locidispid: 5, addr:5) } + let(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) } + let(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) } + + let!(:music_session_1) { FactoryGirl.create(:active_music_session, :creator => creator_1, genre: Genre.find('african'), language: 'en', description: "Bunny Jumps" ) } + let!(:music_session_2) { FactoryGirl.create(:active_music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'es', description: "Play with us as we jam to beatles and bunnies") } + + let(:good_network_score) { 20 } + let(:fair_network_score) { 30 } + let(:tracks) { [{'sound' => 'mono', 'client_track_id' => 'abc', 'instrument_id' => 'piano'}] } + + it "offset/limit" do + # put creators in the session + creator_conn_1.join_the_session(music_session_1.music_session, true, tracks, creator_1, 10) + creator_conn_1.errors.any?.should be_false + creator_conn_2.join_the_session(music_session_2.music_session, true, tracks, creator_2, 10) + creator_conn_2.errors.any?.should be_false + + # set up some scores to control sorting + Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_1.locidispid, creator_conn_1.client_id, creator_conn_1.addr, good_network_score, nil) + Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_2.locidispid, creator_conn_2.client_id, creator_conn_2.addr, fair_network_score, nil) + + # verify we can get all 2 sessions + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + music_sessions[0].should == music_session_1.music_session + + # grab just the 1st + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, offset:0, limit:1) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_1.music_session + + # then the second + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, offset:1, limit:2) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_2.music_session + end + + it "genre" do + # verify we can get all 2 sessions + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + + # get only african + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, genre: 'african') + music_sessions.length.should == 1 + music_sessions[0].genre.should == Genre.find('african') + + # get only ambient + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, genre: 'ambient') + music_sessions.length.should == 1 + music_sessions[0].genre.should == Genre.find('ambient') + end + + it "language" do + # verify we can get all 2 sessions + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + + # get only english + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, lang: 'en') + music_sessions.length.should == 1 + music_sessions[0].language.should == 'en' + + # get only ambient + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, lang: 'es') + music_sessions.length.should == 1 + music_sessions[0].language.should == 'es' + end + + it "keyword" do + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'Jump') + music_sessions.length.should == 1 + music_sessions[0].should == music_session_1.music_session + + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'Bunny') + music_sessions.length.should == 2 + + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'play') + music_sessions.length.should == 1 + + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'bun') + music_sessions.length.should == 2 + end + + it "date" do + music_session_1.music_session.scheduled_start = 1.days.ago + music_session_1.music_session.save! + + # if no day/timezone_offset specified, both should be returned + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + + # find today's session + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, day: Date.today.to_s, timezone_offset: DateTime.now.offset.numerator) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_2.music_session + + + # find yesterday's session + music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, day: (Date.today - 1).to_s, timezone_offset: DateTime.now.offset.numerator) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_1.music_session + end + end + # todo we need more tests: # # the easiest collection of tests, not involving filtering, just tagging and latency, still result in many cases diff --git a/ruby/spec/jam_ruby/models/music_session_spec.rb b/ruby/spec/jam_ruby/models/music_session_spec.rb index 998dcb4a3..fa69af1a3 100644 --- a/ruby/spec/jam_ruby/models/music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/music_session_spec.rb @@ -314,8 +314,8 @@ describe MusicSession do let!(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) } let!(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) } - let!(:music_session_1) { FactoryGirl.create(:music_session, creator: creator_1) } - let!(:music_session_2) { FactoryGirl.create(:music_session, creator: creator_2) } + let!(:music_session_1) { FactoryGirl.create(:music_session, creator: creator_1, genre: Genre.find('african'), language: 'en', description: "Bunny Jumps") } + let!(:music_session_2) { FactoryGirl.create(:music_session, creator: creator_2, genre: Genre.find('ambient'), language: 'es', description: "Play with us as we jam to beatles and bunnies") } let!(:music_session_3) { FactoryGirl.create(:music_session, creator: creator_3) } let(:good_network_score) { 20 } @@ -385,7 +385,122 @@ describe MusicSession do music_sessions, user_scores = sms(searcher_1, {client_id: searcher_conn_1.client_id}) music_sessions.length.should == 2 end + end + describe "parameters" do + let(:creator_1) { FactoryGirl.create(:user, last_jam_locidispid: 4, last_jam_audio_latency: 8) } + let(:creator_conn_1) { FactoryGirl.create(:connection, user: creator_1, ip_address: '4.4.4.4', locidispid: 4, addr:4) } + let(:creator_2) { FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 10) } + let(:creator_conn_2) { FactoryGirl.create(:connection, user: creator_2, ip_address: '4.4.4.4', locidispid: 1, addr:1) } + let(:creator_3) { FactoryGirl.create(:user, last_jam_locidispid: 2, last_jam_audio_latency: 12) } + let(:creator_conn_3) { FactoryGirl.create(:connection, user: creator_3, ip_address: '5.5.5.5', locidispid: 2, addr:2) } + let(:searcher_1) { FactoryGirl.create(:user, last_jam_locidispid: 5, last_jam_audio_latency: 6) } + let(:searcher_conn_1) { FactoryGirl.create(:connection, user: searcher_1, ip_address: '8.8.8.8', locidispid: 5, addr:5) } + let(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) } + let(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) } + + let!(:music_session_1) { FactoryGirl.create(:music_session, :creator => creator_1, genre: Genre.find('african'), language: 'en', description: "Bunny Jumps" ) } + let!(:music_session_2) { FactoryGirl.create(:music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'es', description: "Play with us as we jam to beatles and bunnies") } + + let(:good_network_score) { 20 } + let(:fair_network_score) { 30 } + + it "offset/limit" do + # set up some scores to control sorting + Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_1.locidispid, creator_conn_1.client_id, creator_conn_1.addr, good_network_score, nil) + Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_2.locidispid, creator_conn_2.client_id, creator_conn_2.addr, fair_network_score, nil) + + # verify we can get all 2 sessions + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + music_sessions[0].should == music_session_1 + + # grab just the 1st + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, offset:0, limit:1) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_1 + + # then the second + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, offset:1, limit:2) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_2 + end + + it "genre" do + # verify we can get all 2 sessions + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + + # get only african + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, genre: 'african') + music_sessions.length.should == 1 + music_sessions[0].genre.should == Genre.find('african') + + # get only ambient + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, genre: 'ambient') + music_sessions.length.should == 1 + music_sessions[0].genre.should == Genre.find('ambient') + end + + it "language" do + # verify we can get all 2 sessions + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 2 + + # get only english + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, lang: 'en') + music_sessions.length.should == 1 + music_sessions[0].language.should == 'en' + + # get only ambient + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, lang: 'es') + music_sessions.length.should == 1 + music_sessions[0].language.should == 'es' + end + + it "keyword" do + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'Jump') + music_sessions.length.should == 1 + music_sessions[0].should == music_session_1 + + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'Bunny') + music_sessions.length.should == 2 + + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'play') + music_sessions.length.should == 1 + + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, keyword: 'bun') + music_sessions.length.should == 2 + end + + it "date" do + music_session_1.scheduled_start = 1.days.ago + music_session_1.save! + + # if no day/timezone_offset specified, then the 15 minute slush rule will still kick in, nixing music_session_1 + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_2 + + # find today's session + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, day: Date.today.to_s, timezone_offset: DateTime.now.offset.numerator) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_2 + + # find yesterday's session... oh wait, you can't find a session for yesterday, because the 15 minute slush rule will still kick in. + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, day: (Date.today - 1).to_s, timezone_offset: DateTime.now.offset.numerator) + music_sessions.length.should == 0 + + # but let's make it tomorrow, so we can test in that direction + music_session_1.scheduled_start = 1.day.from_now + music_session_1.save! + + music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, day: (Date.today + 1).to_s, timezone_offset: DateTime.now.offset.numerator) + music_sessions.length.should == 1 + music_sessions[0].should == music_session_1 + + + end end end end diff --git a/web/spec/factories.rb b/web/spec/factories.rb index 8f7291817..a25971bc3 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -77,13 +77,14 @@ FactoryGirl.define do legal_terms true genre JamRuby::Genre.first band nil + language 'en' end before(:create) do |session, evaluator| music_session = FactoryGirl.create(:music_session, name: evaluator.name, description: evaluator.description, fan_chat: evaluator.fan_chat, fan_access: evaluator.fan_access, approval_required: evaluator.approval_required, musician_access: evaluator.musician_access, - genre: evaluator.genre, creator: evaluator.creator, band: evaluator.band) + genre: evaluator.genre, creator: evaluator.creator, band: evaluator.band, language: evaluator.language) session.id = music_session.id end