From 83f3d562381f30d690983b8e04174f2980a38c91 Mon Sep 17 00:00:00 2001 From: Jonathan Kolyer Date: Mon, 23 Feb 2015 06:21:36 +0000 Subject: [PATCH] VRFS-2795 musician search first draft --- ruby/Gemfile | 1 + ruby/lib/jam_ruby.rb | 2 + ruby/lib/jam_ruby/models/json_store.rb | 29 +++ ruby/lib/jam_ruby/models/musician_search.rb | 229 ++++++++++++++++++ ruby/lib/jam_ruby/models/user.rb | 2 + .../models/musician_search_model_spec.rb | 85 +++++++ 6 files changed, 348 insertions(+) create mode 100644 ruby/lib/jam_ruby/models/json_store.rb create mode 100644 ruby/lib/jam_ruby/models/musician_search.rb create mode 100644 ruby/spec/jam_ruby/models/musician_search_model_spec.rb diff --git a/ruby/Gemfile b/ruby/Gemfile index 7a31165d1..48fc5efc2 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -61,6 +61,7 @@ group :test do gem 'resque_spec' #, :path => "/home/jam/src/resque_spec/" gem 'timecop' gem 'rspec-prof' + gem 'time_difference' end # Specify your gem's dependencies in jam_ruby.gemspec diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 4522b6c71..7e12b8c3a 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -202,6 +202,8 @@ require "jam_ruby/models/text_message" require "jam_ruby/jam_tracks_manager" require "jam_ruby/models/performance_sample" require "jam_ruby/models/online_presence" +require "jam_ruby/models/json_store" +require "jam_ruby/models/musician_search" include Jampb diff --git a/ruby/lib/jam_ruby/models/json_store.rb b/ruby/lib/jam_ruby/models/json_store.rb new file mode 100644 index 000000000..12b7a3c89 --- /dev/null +++ b/ruby/lib/jam_ruby/models/json_store.rb @@ -0,0 +1,29 @@ +module JamRuby + class JsonStore < ActiveRecord::Base + self.table_name = 'json_stores' + + serialize :data_blob, JSON + + before_create do + self.data_blob ||= {} + end + + after_save do + @json = nil + end + + attr_accessible :user_id + + belongs_to :user, class_name: "JamRuby::User" + + def json + @json ||= self.data_blob + end + + def update_json_value(key, val) + self.json[key] = val + self.update_attribute(:data_blob, self.json) + end + + end +end diff --git a/ruby/lib/jam_ruby/models/musician_search.rb b/ruby/lib/jam_ruby/models/musician_search.rb new file mode 100644 index 000000000..9900de4ec --- /dev/null +++ b/ruby/lib/jam_ruby/models/musician_search.rb @@ -0,0 +1,229 @@ +module JamRuby + class MusicianSearch < JsonStore + + ANY_VAL_STR = 'any' + ANY_VAL_INT = 0 + + PER_PAGE = 10 + + KEY_GIG = 'gig_count' + KEY_AGES = 'ages' + + SORT_VALS = %W{ latency distance } + SORT_ORDERS = { + SORT_VALS[0] => 'Latency to Me', + SORT_VALS[1] => 'Distance to Me' + } + + SKILL_VALS = [ANY_VAL_STR, 'pro', 'amateur'] + SKILL_LEVELS = { + SKILL_VALS[0] => 'Any', + SKILL_VALS[1] => 'Pro', + SKILL_VALS[2] => 'Amateur' + } + + GIG_COUNTS = [ANY_VAL_INT, 1, 11, 51, 101] + GIG_LABELS = { + GIG_COUNTS[0] => 'Any', + GIG_COUNTS[1] => 'More than 0', + GIG_COUNTS[2] => 'More than 10', + GIG_COUNTS[3] => 'More than 50', + GIG_COUNTS[4] => 'More than 100' + } + + STUDIO_COUNTS = [ANY_VAL_INT, 1, 11, 51, 101] + STUDIOS_LABELS = { + STUDIO_COUNTS[0] => 'Any', + STUDIO_COUNTS[1] => 'More than 0', + STUDIO_COUNTS[2] => 'More than 10', + STUDIO_COUNTS[3] => 'More than 50', + STUDIO_COUNTS[4] => 'More than 100' + } + + AGE_COUNTS = [ANY_VAL_INT, 10, 20, 30, 40, 50] + AGES = { + AGE_COUNTS[0] => 'Any', + AGE_COUNTS[1] => 'Teens', + AGE_COUNTS[2] => "20's", + AGE_COUNTS[3] => "30's", + AGE_COUNTS[4] => "40's", + AGE_COUNTS[5] => "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' + } + + JSON_SCHEMA = { + :sort_order => '', + :instruments => [], + :interests => '', + KEY_GIG => 0, + :studio_session_count => '', + :genres => [], + :skills => '', + KEY_AGES => [] + } + + def self.create_search(user) + ms = self.new + ms.user = user + ms.data_blob = JSON_SCHEMA + ms.save! + ms + end + + def _genres(rel) + gids = json['genres'] + unless gids.blank? + rel = rel.joins("RIGHT JOIN genre_players AS ugenres ON ugenres.player_id = users.id") + .where(['ugenres.genre_id IN ("?") AND users.id IS NOT NULL', gids.join('","')]) + end + rel + end + + def _instruments(rel) + unless (instruments = json['instruments']).blank? + rel = rel.joins("inner JOIN musicians_instruments AS minst ON minst.user_id = users.id") + sql = '' + instruments.each do |inst_level| + sql += "(minst.instrument_id = '#{inst_level['id']}' AND minst.proficiency_level = #{inst_level['level']}) OR " + end + sql.chomp!(' OR ') + rel = rel.where(sql) + 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 + 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") + .where(arels.map(&:where_values).flatten.join(" OR ")) + end + rel + end + + def _studios(rel) + if 0 < (count = json['studio_session_count'].to_i) + rel = rel.where('studio_session_count IS NOT NULL') + case count + when STUDIO_COUNTS[1] + rel = rel.where('studio_session_count >= ? AND studio_session_count < ', + count, STUDIO_COUNTS[2]) + when STUDIO_COUNTS[2] + rel = rel.where('studio_session_count >= ? AND studio_session_count < ', + count, STUDIO_COUNTS[3]) + when STUDIO_COUNTS[3] + rel = rel.where('studio_session_count >= ? AND studio_session_count < ', + count, STUDIO_COUNTS[4]) + when STUDIO_COUNTS[4] + rel = rel.where('studio_session_count >= ?', count) + end + end + rel + end + + def _gigs(rel) + if 0 < (count = json[KEY_GIG].to_i) + rel = rel.where('concert_count IS NOT NULL') + case count + when GIG_COUNTS[1] + rel = rel.where('concert_count >= ? AND concert_count < ', + count, GIG_COUNTS[2]) + when GIG_COUNTS[2] + rel = rel.where('concert_count >= ? AND concert_count < ', + count, GIG_COUNTS[3]) + when GIG_COUNTS[3] + rel = rel.where('concert_count >= ? AND concert_count < ', + count, GIG_COUNTS[4]) + when GIG_COUNTS[4] + rel = rel.where('concert_count >= ?', count) + end + end + rel + end + + def _skills(rel) + if 0 < (count = json['skill_level'].to_i) + rel = rel.where('skill_level IS NOT NULL') + case count + when SKILL_VALS[1] + rel = rel.where('skill_level >= ? AND skill_level < ', + count, SKILL_VALS[2]) + when SKILL_VALS[2] + rel = rel.where('skill_level >= ? AND skill_level < ', + count, SKILL_VALS[3]) + when SKILL_VALS[3] + rel = rel.where('skill_level >= ? AND skill_level < ', + count, SKILL_VALS[4]) + when SKILL_VALS[4] + rel = rel.where('skill_level >= ?', count) + end + end + rel + end + + def _interests(rel) + val = json['interests'] + if val.present? && ANY_VAL_STR != val + rel = rel.joins("RIGHT JOIN genre_players AS interest ON interest.player_id = users.id") + .where(['interest..genre_type = ? AND users.id IS NOT NULL', val]) + end + rel + end + + def pagination(rel, params) + perpage = [(params[:per_page] || PER_PAGE).to_i, 100].min + page = [params[:page].to_i, 1].max + [rel.paginate(:page => page, :per_page => perpage), page] + end + + def do_search(params={}) + rel = User.musicians + 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, page = self.pagination(rel, params) + rel + end + + end +end + diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index d7c5dd90c..5dd7a6fe7 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -155,6 +155,8 @@ module JamRuby has_many :online_presences, :class_name => "JamRuby::OnlinePresence" has_many :performance_samples, :class_name => "JamRuby::PerformanceSample" + has_one :musician_search, :class_name => 'JamRuby::MusicianSearch' + before_save :create_remember_token, :if => :should_validate_password? before_save :stringify_avatar_info , :if => :updating_avatar diff --git a/ruby/spec/jam_ruby/models/musician_search_model_spec.rb b/ruby/spec/jam_ruby/models/musician_search_model_spec.rb new file mode 100644 index 000000000..eb12500a1 --- /dev/null +++ b/ruby/spec/jam_ruby/models/musician_search_model_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' +require 'time_difference' +require 'byebug' + +describe 'Musician Search Model' do + let!(:searcher) { FactoryGirl.create(:austin_user) } + let!(:search) { MusicianSearch.create_search(searcher) } + + before(:all) do + User.delete_all + @users = [] + today = Date.today + MusicianSearch::AGE_COUNTS.each_with_index do |age, idx| + age = 0==idx ? MusicianSearch::AGE_COUNTS[1] : age + dd = today - age.years - 1.day + @users << FactoryGirl.create(:austin_user, :birth_date => dd) + @users << FactoryGirl.create(:dallas_user, :birth_date => dd) + @users << FactoryGirl.create(:miami_user, :birth_date => dd) + @users << FactoryGirl.create(:seattle_user, :birth_date => dd) + end + end + + describe "creates search obj" do + it "associates to user" do + expect(search.user).to eq(searcher) + searcher.reload + expect(searcher.musician_search).to eq(search) + end + + it "sets json" do + search.update_json_value(MusicianSearch::KEY_GIG, MusicianSearch::GIG_COUNTS[1]) + expect(search.json[MusicianSearch::KEY_GIG]).to eq(MusicianSearch::GIG_COUNTS[1]) + end + end + + describe "filtering criteria" do + + it "filters musicians" do + expect(search.do_search(per_page: User.musicians.count).count).to eq(User.musicians.count) + end + + it "filters ages" do + age = MusicianSearch::AGE_COUNTS[1] + search.update_json_value(MusicianSearch::KEY_AGES, [age]) + today = Date.today.to_time + search.do_search.all.each do |uu| + diff = TimeDifference.between(uu.birth_date.to_time, today).in_years + expect(diff).to be >= age + expect(diff).to be < MusicianSearch::AGE_COUNTS[2] + end + search.update_json_value(MusicianSearch::KEY_AGES, [0]) + search.do_search.to_sql =~ /(birth_date)/ + expect($1).to eq(nil) + end + + it "filters genres" do + end + it "filters studio sessions" do + end + it "filters gigs" do + end + it "filters skills" do + end + it "filters instruments" do + end + it "filters interests" do + end + it "filters combinations" do + end + end + + describe "pagination" do + it "first page results" do + end + it "second page results" do + end + end + + describe "sort order" do + before(:each) do + create_phony_database + end + + end +end