jam-cloud/ruby/lib/jam_ruby/models/musician_search.rb

415 lines
12 KiB
Ruby

module JamRuby
class MusicianSearch < JsonStore
attr_accessor :page_count, :results, :user_counters, :page_number
ANY_VAL_STR = 'any'
ANY_VAL_INT = -1
PER_PAGE = 10
PG_SMALLINT_MAX = 32767
KEY_GIGS = 'concert_gigs'
KEY_STUDIOS = 'studio_sessions'
KEY_AGES = 'ages'
KEY_SKILL = 'skill_level'
KEY_GENRES = 'genres'
KEY_INSTRUMENTS = 'instruments'
KEY_INTERESTS = 'interests'
KEY_SORT_ORDER = 'sort_order'
SORT_VALS = %W{ latency distance }
SORT_ORDERS = {
SORT_VALS[0] => 'Latency to Me',
SORT_VALS[1] => 'Distance to Me'
}
SKILL_VALS = [ANY_VAL_INT, 1, 2]
SKILL_LEVELS = {
SKILL_VALS[0] => 'Any',
SKILL_VALS[1] => 'Amateur',
SKILL_VALS[2] => 'Pro',
}
GIG_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4]
GIG_LABELS = {
GIG_COUNTS[0] => 'Any',
GIG_COUNTS[1] => 'under 10',
GIG_COUNTS[2] => '10 to 50',
GIG_COUNTS[3] => '50 to 100',
GIG_COUNTS[4] => 'over 100'
}
STUDIO_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4]
STUDIOS_LABELS = {
STUDIO_COUNTS[0] => 'Any',
STUDIO_COUNTS[1] => 'under 10',
STUDIO_COUNTS[2] => '10 to 50',
STUDIO_COUNTS[3] => '50 to 100',
STUDIO_COUNTS[4] => 'over 100'
}
AGE_COUNTS = [10, 20, 30, 40, 50]
AGES = {
AGE_COUNTS[0] => 'Teens',
AGE_COUNTS[1] => "20's",
AGE_COUNTS[2] => "30's",
AGE_COUNTS[3] => "40's",
AGE_COUNTS[4] => "50+"
}
INTEREST_VALS = [ANY_VAL_STR,
GenrePlayer::VIRTUAL_BAND,
GenrePlayer::TRADITIONAL_BAND,
GenrePlayer::PAID_SESSION,
GenrePlayer::FREE_SESSION,
GenrePlayer::COWRITING,
]
INTERESTS = {
INTEREST_VALS[0] => 'Any',
INTEREST_VALS[1] => 'Virtual Band',
INTEREST_VALS[2] => 'Traditional Band',
INTEREST_VALS[3] => 'Paid Sessions',
INTEREST_VALS[4] => 'Free Sessions',
INTEREST_VALS[5] => 'Co-Writing'
}
INSTRUMENT_PROFICIENCY = {
1 => 'Beginner',
2 => 'Intermediate',
3 => 'Expert',
}
JSON_SCHEMA = {
KEY_SORT_ORDER => SORT_VALS[0],
KEY_INSTRUMENTS => [],
KEY_INTERESTS => INTEREST_VALS[0],
KEY_GENRES => [],
KEY_GIGS => GIG_COUNTS[0].to_s,
KEY_STUDIOS => STUDIO_COUNTS[0].to_s,
KEY_SKILL => SKILL_VALS[0].to_s,
KEY_AGES => []
}
JSON_SCHEMA_KEYS = JSON_SCHEMA.keys
MULTI_VALUE_KEYS = JSON_SCHEMA.collect { |kk,vv| vv.is_a?(Array) ? kk : nil }.compact
SINGLE_VALUE_KEYS = JSON_SCHEMA.keys - MULTI_VALUE_KEYS
SEARCH_FILTER_META = {
per_page: PER_PAGE,
filter_keys: {
keys: JSON_SCHEMA_KEYS,
multi: MULTI_VALUE_KEYS,
single: SINGLE_VALUE_KEYS,
},
sort_order: { keys: SORT_VALS, map: SORT_ORDERS },
interests: { keys: INTEREST_VALS, map: INTERESTS },
ages: { keys: AGE_COUNTS, map: AGES }
}
def self.user_search_filter(user)
unless ms = user.musician_search
ms = self.create_search(user)
end
ms
end
def self.search_filter_json(user)
self.user_search_filter(user).json
end
def self.create_search(user)
ms = self.new
ms.user = user
ms.data_blob = JSON_SCHEMA
ms.save!
ms
end
# XXX SQL INJECTION
def _genres(rel)
gids = json[KEY_GENRES]
unless gids.blank?
gidsql = gids.join("','")
gpsql = "SELECT player_id FROM genre_players WHERE (player_type = 'JamRuby::User' AND genre_id IN ('#{gidsql}'))"
rel = rel.where("users.id IN (#{gpsql})")
end
rel
end
# XXX SQL INJECTION
def _instruments(rel)
unless (instruments = json['instruments']).blank?
instsql = "SELECT player_id FROM musicians_instruments WHERE (("
instsql += instruments.collect do |inst|
"instrument_id = '#{inst['id']}' AND proficiency_level = #{inst['level']}"
end.join(") OR (")
instsql += "))"
rel = rel.where("users.id IN (#{instsql})")
end
rel
end
def _ages(rel)
unless (vals = json[KEY_AGES]).blank?
return rel if vals.detect { |vv| ANY_VAL_INT == vv }
arels = []
vals.each do |val|
today = Date.today
case val.to_i
when 10
arels << User.where("birth_date >= ? AND birth_date < ?",
today - 20.years, today - 10.years)
when 20
arels << User.where("birth_date >= ? AND birth_date < ?",
today - 30.years, today - 20.years)
when 30
arels << User.where("birth_date >= ? AND birth_date < ?",
today - 40.years, today - 50.years)
when 40
arels << User.where("birth_date >= ? AND birth_date < ?",
today - 50.years, today - 40.years)
when 50
arels << User.where("birth_date <= ?", today - 50.years)
end
end
rel = rel.where("birth_date IS NOT NULL")
sql = "(#{arels.map(&:where_values).flatten.join(') OR (')})"
rel = rel.where(sql)
end
rel
end
def _studios(rel)
ss = json[KEY_STUDIOS].to_i
rel = rel.where('studio_session_count = ?', ss) if 0 <= ss
rel
end
def _gigs(rel)
gg = json[KEY_GIGS].to_i
rel = rel.where('concert_count = ?',gg) if 0 <= gg
rel
end
def _skills(rel)
if 0 < (val = json[KEY_SKILL].to_i)
rel = rel.where(['skill_level = ?', val])
end
rel
end
def _interests(rel)
val = json[KEY_INTERESTS]
if val.present? && ANY_VAL_STR != val
rel = rel.where("#{val} = ?", true)
end
rel
end
def _sort_order(rel)
val = json[KEY_SORT_ORDER]
if SORT_VALS[1] == val
locidispid = self.user.last_jam_locidispid || 0
my_locid = locidispid / 1000000
rel = rel.joins("LEFT JOIN geoiplocations AS my_geo ON my_geo.locid = #{my_locid}")
rel = rel.joins("LEFT JOIN geoiplocations AS other_geo ON users.last_jam_locidispid/1000000 = other_geo.locid")
rel = rel.group("users.id, my_geo.geog, other_geo.geog")
rel = rel.order('st_distance(my_geo.geog, other_geo.geog)')
else
rel = rel.joins("LEFT JOIN current_scores ON current_scores.a_userid = users.id AND current_scores.b_userid = '#{self.user.id}'")
rel = rel.order('current_scores.full_score ASC')
end
rel
end
def do_search(params={})
rel = User.musicians.where('users.id <> ?', self.user.id)
rel = self._genres(rel)
rel = self._ages(rel)
rel = self._studios(rel)
rel = self._gigs(rel)
rel = self._skills(rel)
rel = self._instruments(rel)
rel = self._interests(rel)
rel = self._sort_order(rel)
rel
end
def search_results_page(filter=nil, page=1)
if filter
self.data_blob = filter
self.save
else
filter = self.data_blob
end
rel = do_search(filter)
@page_number = [page.to_i, 1].max
rel = rel.paginate(:page => @page_number, :per_page => PER_PAGE)
rel = rel.includes([:instruments, :followings, :friends])
@page_count = rel.total_pages
musician_results(rel.all)
end
def reset_filter
self.data_blob = JSON_SCHEMA
self.save
end
def reset_search_results
reset_filter
search_results_page
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(_results)
@results = _results
@user_counters = {} and return self unless user
@user_counters = @results.inject({}) { |hh,val| hh[val.id] = []; hh }
mids = "'#{@results.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] = Follow.where(:followable_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 follows ON follows.user_id = '#{user.id}'")
rel = rel.where(["users.id IN (#{mids}) AND follows.followable_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 }
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 search_type
self.class.to_s
end
def is_blank?
self.data_blob == JSON_SCHEMA
end
def description
if self.is_blank?
return 'Click search button to look for musicians with similar interests, skill levels, etc.'
end
jj = self.json
str = ''
if 0 < (val = jj[KEY_INSTRUMENTS]).length
str += ", Instruments = "
instr_ids = val.collect { |stored_instrument| stored_instrument['id'] }
instrs = Instrument.where(["id IN (?)", instr_ids]).order(:description)
instrs.each_with_index do |ii, idx|
proficiency = val.detect { |stored_instrument| stored_instrument['id'] == ii.id }['level']
str += "#{ii.description} / #{INSTRUMENT_PROFICIENCY[proficiency.to_i]}"
str += ', ' unless idx==(instrs.length-1)
end
end
if (val = jj[KEY_INTERESTS]) != INTEREST_VALS[0]
str += ", Interest = #{INTERESTS[val]}"
end
if (val = jj[KEY_SKILL].to_i) != SKILL_VALS[0]
str += ", Skill = #{SKILL_LEVELS[val]}"
end
if (val = jj[KEY_STUDIOS].to_i) != STUDIO_COUNTS[0]
str += ", Studio Sessions = #{STUDIOS_LABELS[val]}"
end
if (val = jj[KEY_GIGS].to_i) != GIG_COUNTS[0]
str += ", Concert Gigs = #{GIG_LABELS[val]}"
end
val = jj[KEY_AGES].map(&:to_i)
val.sort!
if !val.blank?
str += ", Ages = "
val.each_with_index do |vv, idx|
str += "#{AGES[vv]}"
str += ', ' unless idx==(val.length-1)
end
end
if 0 < (val = jj[KEY_GENRES]).length
str += ", Genres = "
genres = Genre.where(["id IN (?)", val]).order('description').pluck(:description)
genres.each_with_index do |gg, idx|
str += "#{gg}"
str += ', ' unless idx==(genres.length-1)
end
end
str += ", Sort = #{SORT_ORDERS[json_value(MusicianSearch::KEY_SORT_ORDER)]}"
if str.start_with?(', ')
# trim off any leading ,
str = str[2..-1]
end
str = 'Current Search: ' + str
str
end
end
end