module JamRuby # not a active_record model; just a search result class Search attr_accessor :bands, :musicians, :fans, :recordings, :friends, :search_type LIMIT = 10 # performs a site-white search def self.search(query, user_id = nil) 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) 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 # search_results - results from a Tire search across band/user/recording def initialize(search_results=nil) @bands = [] @musicians = [] @fans = [] @recordings = [] @friends = [] if search_results.nil? return end search_results.take(LIMIT).each do |result| if result.class == User if result.musician @musicians.push(result) @search_type = PARAM_MUSICIAN 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 end def self.create_tsquery(query) # empty queries don't hit back to elasticsearch if query.nil? || query.length == 0 return nil end search_terms = query.split if search_terms.length == 0 return nil end args = nil search_terms.each do |search_term| if args == nil args = search_term else args = args + " & " + search_term end end args = args + ":*" return args end attr_accessor :user_counters, :page_num, :page_count PARAM_MUSICIAN = :srch_m PARAM_BAND = :srch_b B_PER_PAGE = M_PER_PAGE = 10 M_MILES_DEFAULT = 500 B_MILES_DEFAULT = 0 M_ORDER_FOLLOWS = ['Most Followed', :followed] M_ORDER_PLAYS = ['Most Plays', :plays] M_ORDER_PLAYING = ['Playing Now', :playing] ORDERINGS = B_ORDERINGS = M_ORDERINGS = [M_ORDER_FOLLOWS, M_ORDER_PLAYS, M_ORDER_PLAYING] B_ORDERING_KEYS = M_ORDERING_KEYS = M_ORDERINGS.collect { |oo| oo[1] } DISTANCE_OPTS = B_DISTANCE_OPTS = M_DISTANCE_OPTS = [['Any', 0], [1000.to_s, 1000], [500.to_s, 500], [250.to_s, 250], [100.to_s, 100], [50.to_s, 50], [25.to_s, 25]] def self.order_param(params, keys=M_ORDERING_KEYS) ordering = params[:orderby] ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering } end def self.musician_search(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") .where(['minst.instrument_id = ? AND users.id IS NOT NULL', instrument]) end rel = MaxMindGeo.where_latlng(rel, params, current_user) sel_str = 'users.*' case ordering = self.order_param(params) when :plays # FIXME: double counting? sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}" rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id") .joins("LEFT JOIN recordings AS records ON records.owner_id = users.id") .group("users.id") .order("play_count DESC, users.created_at DESC") when :followed sel_str = "COUNT(follows) AS search_follow_count, #{sel_str}" rel = rel.joins("LEFT JOIN users_followers AS follows ON follows.user_id = users.id") .group("users.id") .order("COUNT(follows) DESC, users.created_at DESC") when :playing rel = rel.joins("LEFT JOIN connections ON connections.user_id = users.id") .where(['connections.music_session_id IS NOT NULL AND connections.aasm_state != ?', 'expired']) .order("users.created_at DESC") end rel = rel.select(sel_str) perpage = [(params[:per_page] || M_PER_PAGE).to_i, 100].min page = [params[:page].to_i, 1].max rel = rel.paginate(:page => page, :per_page => perpage) rel = rel.includes([:instruments, :followings, :friends]) objs = rel.all srch = Search.new srch.page_num, srch.page_count = page, objs.total_pages srch.musician_results_for_user(objs, current_user) end RESULT_FOLLOW = :follows RESULT_FRIEND = :friends COUNT_FRIEND = :count_friend COUNT_FOLLOW = :count_follow COUNT_RECORD = :count_record COUNT_SESSION = :count_session COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION] def musician_results_for_user(results, user) @search_type, @musicians = PARAM_MUSICIAN, results if user @user_counters = results.inject({}) { |hh,val| hh[val.id] = []; hh } mids = "'#{@musicians.map(&:id).join("','")}'" # this gets counts for each search result on friends/follows/records/sessions results.each do |uu| counters = { } counters[COUNT_FRIEND] = Friendship.where(:user_id => uu.id).count counters[COUNT_FOLLOW] = UserFollowing.where(:user_id => uu.id).count counters[COUNT_RECORD] = ClaimedRecording.where(:user_id => uu.id).count counters[COUNT_SESSION] = MusicSession.where(:user_id => uu.id).count @user_counters[uu.id] << counters end # this section determines follow/like/friend status for each search result # so that action links can be activated or not rel = User.select("users.id AS uid") rel = rel.joins("LEFT JOIN users_followers AS follows ON follows.follower_id = '#{user.id}'") rel = rel.where(["users.id IN (#{mids}) AND follows.user_id = users.id"]) rel.all.each { |val| @user_counters[val.uid] << RESULT_FOLLOW } rel = User.select("users.id AS uid") rel = rel.joins("LEFT JOIN friendships AS friends ON friends.friend_id = '#{user.id}'") rel = rel.where(["users.id IN (#{mids}) AND friends.user_id = users.id"]) rel.all.each { |val| @user_counters[val.uid] << RESULT_FRIEND } else @user_counters = {} end self end private def _count(musician, key) if mm = @user_counters[musician.id] return mm.detect { |ii| ii.is_a?(Hash) }[key] end if @user_counters 0 end public def follow_count(musician) _count(musician, COUNT_FOLLOW) end def friend_count(musician) _count(musician, COUNT_FRIEND) end def record_count(musician) _count(musician, COUNT_RECORD) end def session_count(musician) _count(musician, COUNT_SESSION) end def is_friend?(musician) if mm = @user_counters[musician.id] return mm.include?(RESULT_FRIEND) end if @user_counters false end def is_follower?(musician) if mm = @user_counters[musician.id] return mm.include?(RESULT_FOLLOW) end if @user_counters false end def self.new_musicians(usr, since_date=Time.now - 1.week, max_count=50, radius=M_MILES_DEFAULT) rel = User.musicians .where(['created_at >= ? AND users.id != ?', since_date, usr.id]) .within(radius, :origin => [usr.lat, usr.lng]) .order('created_at DESC') .limit(max_count) objs = rel.all.to_a if block_given? yield(objs) if 0 < objs.count else return objs end end def self.band_search(params={}, current_user=nil) rel = Band.scoped # rel = Arel::Table.new(:bands) unless (genre = params[:genre]).blank? rel = Band.joins("RIGHT JOIN band_genres AS bgenres ON bgenres.band_id = bands.id") .where(['bgenres.genre_id = ? AND bands.id IS NOT NULL', genre]) end rel = MaxMindGeo.where_latlng(rel, params, current_user) sel_str = 'bands.*' case ordering = self.order_param(params) when :plays # FIXME: double counting? sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}" rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id") .joins("LEFT JOIN recordings AS records ON records.owner_id = users.id") .group("users.id") .order("play_count DESC, users.created_at DESC") when :followed sel_str = "COUNT(follows) AS search_follow_count, #{sel_str}" rel = rel.joins("LEFT JOIN bands_followers AS follows ON follows.band_id = bands.id") .group("bands.id") .order("COUNT(follows) DESC, bands.created_at DESC") when :playing rel = rel.joins("LEFT JOIN connections ON connections.user_id = users.id") .where(['connections.music_session_id IS NOT NULL AND connections.aasm_state != ?', 'expired']) .order("users.created_at DESC") end rel = rel.select(sel_str) perpage = [(params[:per_page] || M_PER_PAGE).to_i, 100].min page = [params[:page].to_i, 1].max rel = rel.paginate(:page => page, :per_page => perpage) rel = rel.includes([:users]) objs = rel.all srch = Search.new srch.page_num, srch.page_count = page, objs.total_pages srch.band_results_for_user(objs, current_user) end def band_results_for_user(results, user) @search_type, @bands = PARAM_BAND, results if user @user_counters = results.inject({}) { |hh,val| hh[val.id] = []; hh } mids = "'#{@bands.map(&:id).join("','")}'" # this gets counts for each search result results.each do |bb| counters = { } counters[COUNT_FOLLOW] = BandFollowing.where(:band_id => bb.id).count # counters[COUNT_RECORD] = ClaimedRecording.where(:band_id => bb.id).count # counters[COUNT_SESSION] = MusicSession.where(:band_id => bb.id).count @user_counters[bb.id] << counters end # this section determines follow/like/friend status for each search result # so that action links can be activated or not rel = Band.select("bands.id AS bid") rel = rel.joins("LEFT JOIN bands_followers AS follows ON follows.follower_id = '#{user.id}'") rel = rel.where(["bands.id IN (#{mids}) AND follows.band_id = bands.id"]) rel.all.each { |val| @user_counters[val.bid] << RESULT_FOLLOW } else @user_counters = {} end self end end end