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 M_PER_PAGE = 10 M_MILES_DEFAULT = 500 M_ORDER_FOLLOWS = ['Most Followed', :followed] M_ORDER_PLAYS = ['Most Plays', :plays] M_ORDER_PLAYING = ['Playing Now', :playing] M_ORDERINGS = [M_ORDER_FOLLOWS, M_ORDER_PLAYS, M_ORDER_PLAYING] M_ORDERING_KEYS = M_ORDERINGS.collect { |oo| oo[1] } def self.musician_order_param(params) ordering = params[:orderby] ordering.blank? ? M_ORDERING_KEYS[0] : M_ORDERING_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 location_distance, location_city = params[:distance], params[:city] distance, latlng = nil, [] if location_distance && location_city if geo = MaxMindGeo.where(:city => params[:city]).limit(1).first distance, latlng = location_distance, [geo.lat, geo.lng] end elsif current_user if current_user.lat.nil? || current_user.lng.nil? if params[:remote_ip] && (geo = MaxMindGeo.ip_lookup(params[:remote_ip])) latlng = [geo.lat, geo.lng] if geo.lat && geo.lng end else latlng = [current_user.lat, current_user.lng] end elsif params[:remote_ip] && (geo = MaxMindGeo.ip_lookup(params[:remote_ip])) latlng = [geo.lat, geo.lng] if geo.lat && geo.lng end if latlng.present? distance ||= location_distance || M_MILES_DEFAULT rel = rel.where(['lat IS NOT NULL AND lng IS NOT NULL']) .within(distance, :origin => latlng) end sel_str = 'users.*' case ordering = self.musician_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_LIKE = :likes 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 users_likers AS likers ON likers.liker_id = '#{user.id}'") rel = rel.where(["users.id IN (#{mids}) AND likers.user_id = users.id"]) rel.all.each { |val| @user_counters[val.uid] << RESULT_LIKE } 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 is_liker?(musician) if mm = @user_counters[musician.id] return mm.include?(RESULT_LIKE) 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 end end