505 lines
20 KiB
Ruby
505 lines
20 KiB
Ruby
module JamRuby
|
|
class Teacher < ActiveRecord::Base
|
|
include HtmlSanitize
|
|
html_sanitize strict: [:biography, :website]
|
|
attr_accessor :validate_introduction, :validate_basics, :validate_pricing
|
|
attr_accessible :genres, :teacher_experiences, :experiences_teaching, :experiences_education, :experiences_award
|
|
has_many :genres, :class_name => "JamRuby::Genre", :through => :teachers_genres # , :order => "description"
|
|
has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre"
|
|
has_many :instruments, :class_name => "JamRuby::Instrument", through: :teachers_instruments # , :order => "description"
|
|
has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument"
|
|
has_many :subjects, :class_name => "JamRuby::Subject", :through => :teachers_subjects # , :order => "description"
|
|
has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject"
|
|
has_many :languages, :class_name => "JamRuby::Language", :through => :teachers_languages # , :order => "description"
|
|
has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage"
|
|
has_many :teacher_experiences, :class_name => "JamRuby::TeacherExperience"
|
|
has_many :experiences_teaching, -> { where(experience_type: 'teaching') }, :class_name => "JamRuby::TeacherExperience"
|
|
has_many :experiences_education, -> { where(experience_type: 'education') }, :class_name => "JamRuby::TeacherExperience"
|
|
has_many :experiences_award, -> { where(experience_type: 'award') }, :class_name => "JamRuby::TeacherExperience"
|
|
has_many :reviews, :class_name => "JamRuby::Review", as: :target
|
|
has_many :lesson_sessions, :class_name => "JamRuby::LessonSession"
|
|
has_many :lesson_package_purchases, :class_name => "JamRuby::LessonPackagePurchase"
|
|
has_one :review_summary, :class_name => "JamRuby::ReviewSummary", as: :target
|
|
has_one :user, :class_name => 'JamRuby::User', foreign_key: :teacher_id, inverse_of: :teacher
|
|
belongs_to :school, :class_name => "JamRuby::School", inverse_of: :teachers
|
|
belongs_to :retailer, :class_name => "JamRuby::Retailer", inverse_of: :teachers
|
|
|
|
validates :user, :presence => true
|
|
validates :biography, length: {minimum: 5, maximum: 4096}, :if => :validate_introduction
|
|
validates :introductory_video, :format => {:with => /(?:https?:\/\/)?(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=)?([\w-]{10,})/, message: "is not a valid youtube URL"}, :allow_blank => true, :if => :validate_introduction
|
|
validates :years_teaching, :presence => true, :if => :validate_introduction
|
|
validates :years_playing, :presence => true, :if => :validate_introduction
|
|
validates :teaches_test_drive, inclusion: {in: [true, false]}, :if => :validate_pricing
|
|
validates :top_rated, inclusion: {in: [true, false]}
|
|
validates :test_drives_per_week, numericality: {only_integer: true, minimum: 2, maximum: 10}, :if => :validate_pricing
|
|
validates :instruments, :length => {minimum: 1, message: "At least one instrument or subject is required"}, if: :validate_basics, unless: ->(teacher) { teacher.subjects.length>0 }
|
|
validates :subjects, :length => {minimum: 1, message: "At least one instrument or subject is required"}, if: :validate_basics, unless: ->(teacher) { teacher.instruments.length>0 }
|
|
validates :genres, :length => {minimum: 1, message: "At least one genre is required"}, if: :validate_basics
|
|
validates :languages, :length => {minimum: 1, message: "At least one language is required"}, if: :validate_basics
|
|
|
|
validate :offer_pricing, :if => :validate_pricing
|
|
validate :offer_duration, :if => :validate_pricing
|
|
validate :teaches_ages, :if => :validate_basics
|
|
|
|
#default_scope { includes(:genres).order('created_at desc') }
|
|
|
|
after_save :update_profile_pct
|
|
|
|
def update_profile_pct
|
|
result = pct_complete
|
|
self.profile_pct = result[:pct]
|
|
self.profile_pct_summary = result.to_json
|
|
Teacher.where(id: id).update_all(profile_pct: self.profile_pct, profile_pct_summary: self.profile_pct_summary)
|
|
end
|
|
|
|
def is_guitar_center?
|
|
return school && school.is_guitar_center?
|
|
end
|
|
|
|
def self.index(user, params = {})
|
|
limit = params[:per_page]
|
|
limit ||= 20
|
|
limit = limit.to_i
|
|
|
|
query = User.unscoped.joins(:teacher)
|
|
|
|
# only show teachers with ready for session set to true
|
|
query = query.where('teachers.ready_for_session_at IS NOT NULL').where('teachers.is_searchable = TRUE')
|
|
|
|
# always force GuitarCenter users to see only their school's teachers, regardless of what they picked
|
|
if user && (user.is_guitar_center_student? || (params[:onlyMySchool] && params[:onlyMySchool] != 'false' && user.school_id))
|
|
query = query.where("teachers.school_id = ?", user.school_id)
|
|
end
|
|
|
|
instruments = params[:instruments]
|
|
if instruments && !instruments.blank? && instruments.length > 0
|
|
query = query.joins("inner JOIN teachers_instruments AS tinst ON tinst.teacher_id = teachers.id")
|
|
.where("tinst.instrument_id IN (?)", instruments)
|
|
end
|
|
|
|
subjects = params[:subjects]
|
|
if subjects && !subjects.blank? && subjects.length > 0
|
|
query = query.joins("inner JOIN teachers_subjects AS tsubjs ON tsubjs.teacher_id = teachers.id")
|
|
.where('tsubjs.subject_id IN (?)', subjects)
|
|
end
|
|
|
|
genres = params[:genres]
|
|
if genres && !genres.blank? && genres.length > 0
|
|
query = query.joins("inner JOIN teachers_genres AS tgenres ON tgenres.teacher_id = teachers.id")
|
|
.where('tgenres.genre_id IN (?)', genres)
|
|
end
|
|
|
|
country = params[:country]
|
|
if country && country.length > 0
|
|
query = query.where(country: country)
|
|
end
|
|
|
|
region = params[:region]
|
|
if region && region.length > 0
|
|
query = query.where(state: region)
|
|
end
|
|
|
|
languages = params[:languages]
|
|
if languages && !languages.blank? && languages.length > 0
|
|
query= query.joins("inner JOIN teachers_languages AS tlang ON tlang.teacher_id = teachers.id")
|
|
.where('tlang.language_id IN (?)', languages)
|
|
end
|
|
|
|
years_teaching = params[:years_teaching].to_i
|
|
if params[:years_teaching] && years_teaching > 0
|
|
query = query.where('years_teaching >= ?', years_teaching)
|
|
end
|
|
|
|
teaches_beginner = params[:teaches_beginner]
|
|
teaches_intermediate = params[:teaches_intermediate]
|
|
teaches_advanced = params[:teaches_advanced]
|
|
|
|
if teaches_beginner.present? || teaches_intermediate.present? || teaches_advanced.present?
|
|
|
|
clause = ''
|
|
|
|
if teaches_beginner == true
|
|
clause << 'teaches_beginner = true'
|
|
end
|
|
if teaches_intermediate == true
|
|
if clause.length > 0
|
|
clause << ' OR '
|
|
end
|
|
clause << 'teaches_intermediate = true'
|
|
end
|
|
if teaches_advanced == true
|
|
if clause.length > 0
|
|
clause << ' OR '
|
|
end
|
|
clause << 'teaches_advanced = true'
|
|
end
|
|
query = query.where(clause)
|
|
end
|
|
|
|
student_age = params[:student_age].to_i
|
|
if params[:student_age] && student_age > 0
|
|
query = query.where("teaches_age_lower <= ? AND (CASE WHEN teaches_age_upper = 0 THEN true ELSE teaches_age_upper >= ? END)", student_age, student_age)
|
|
end
|
|
|
|
# don't show phantom teachers that teach 'bass guitar', 'acoustic guitar', 'electric guitar'
|
|
query = query.where("((select count(checkgt.instrument_id) from teachers_instruments checkgt where checkgt.teacher_id = teachers.id AND checkgt.instrument_id IN ('bass guitar', 'acoustic guitar', 'electric guitar') ) = 0 AND phantom = true) OR phantom = false")
|
|
|
|
# order in this way: https://jamkazam.atlassian.net/browse/VRFS-4058
|
|
# Real teachers who are marked as top.
|
|
# Real teachers who are not marked as top.
|
|
# Phantom teachers.
|
|
query = query.order("top_rated DESC, phantom ASC")
|
|
|
|
current_page = params[:page].nil? ? 1 : params[:page].to_i
|
|
next_page = current_page + 1
|
|
|
|
# will_paginate gem
|
|
query = query.paginate(:page => current_page, :per_page => limit)
|
|
|
|
if query.length == 0 # no more results
|
|
{query: query, next_page: nil}
|
|
elsif query.length < limit # no more results
|
|
{query: query, next_page: nil}
|
|
else
|
|
{query: query, next_page: next_page}
|
|
end
|
|
end
|
|
|
|
def self.save_teacher(user, params)
|
|
teacher = nil
|
|
Teacher.transaction do
|
|
teacher = build_teacher(user, params)
|
|
if teacher.save
|
|
# flag the user as a teacher
|
|
teacher.user.is_a_teacher = true
|
|
teacher.user.save(validate: false)
|
|
end
|
|
|
|
if teacher.errors.any?
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
end
|
|
teacher
|
|
|
|
end
|
|
|
|
def self.build_teacher(user, params)
|
|
# ensure person creating this Teacher is a Musician
|
|
unless user && user.musician?
|
|
raise JamPermissionError, "must be a musician"
|
|
end
|
|
|
|
teacher = user.teacher
|
|
teacher ||= Teacher.new
|
|
teacher.user = user
|
|
|
|
teacher.website = params[:website] if params.key?(:website)
|
|
teacher.biography = params[:biography] if params.key?(:biography)
|
|
teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video)
|
|
teacher.years_teaching = params[:years_teaching] if params.key?(:years_teaching)
|
|
teacher.years_playing = params[:years_playing] if params.key?(:years_playing)
|
|
teacher.teaches_age_lower = params[:teaches_age_lower] if params.key?(:teaches_age_lower)
|
|
teacher.teaches_age_upper = params[:teaches_age_upper] if params.key?(:teaches_age_upper)
|
|
teacher.teaches_beginner = params[:teaches_beginner] if params.key?(:teaches_beginner)
|
|
teacher.teaches_intermediate = params[:teaches_intermediate] if params.key?(:teaches_intermediate)
|
|
teacher.teaches_advanced = params[:teaches_advanced] if params.key?(:teaches_advanced)
|
|
teacher.prices_per_lesson = params[:prices_per_lesson] if params.key?(:prices_per_lesson)
|
|
teacher.prices_per_month = params[:prices_per_month] if params.key?(:prices_per_month)
|
|
teacher.lesson_duration_30 = params[:lesson_duration_30] if params.key?(:lesson_duration_30)
|
|
teacher.lesson_duration_45 = params[:lesson_duration_45] if params.key?(:lesson_duration_45)
|
|
teacher.lesson_duration_60 = params[:lesson_duration_60] if params.key?(:lesson_duration_60)
|
|
teacher.lesson_duration_90 = params[:lesson_duration_90] if params.key?(:lesson_duration_90)
|
|
teacher.lesson_duration_120 = params[:lesson_duration_120] if params.key?(:lesson_duration_120)
|
|
teacher.price_per_lesson_30_cents = params[:price_per_lesson_30_cents] if params.key?(:price_per_lesson_30_cents)
|
|
teacher.price_per_lesson_45_cents = params[:price_per_lesson_45_cents] if params.key?(:price_per_lesson_45_cents)
|
|
teacher.price_per_lesson_60_cents = params[:price_per_lesson_60_cents] if params.key?(:price_per_lesson_60_cents)
|
|
teacher.price_per_lesson_90_cents = params[:price_per_lesson_90_cents] if params.key?(:price_per_lesson_90_cents)
|
|
teacher.price_per_lesson_120_cents = params[:price_per_lesson_120_cents] if params.key?(:price_per_lesson_120_cents)
|
|
teacher.price_per_month_30_cents = params[:price_per_month_30_cents] if params.key?(:price_per_month_30_cents)
|
|
teacher.price_per_month_45_cents = params[:price_per_month_45_cents] if params.key?(:price_per_month_45_cents)
|
|
teacher.price_per_month_60_cents = params[:price_per_month_60_cents] if params.key?(:price_per_month_60_cents)
|
|
teacher.price_per_month_90_cents = params[:price_per_month_90_cents] if params.key?(:price_per_month_90_cents)
|
|
teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents)
|
|
teacher.teaches_test_drive = params[:teaches_test_drive] if params.key?(:teaches_test_drive)
|
|
teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week)
|
|
teacher.is_searchable = params[:is_searchable] if params.key?(:is_searchable)
|
|
teacher.test_drives_per_week = 10 if !params.key?(:test_drives_per_week) # default to 10 in absence of others
|
|
if params.key?(:school_id)
|
|
teacher.school_id = params[:school_id]
|
|
if !teacher.joined_school_at
|
|
teacher.joined_school_at = Time.now
|
|
end
|
|
end
|
|
if params.key?(:retailer_id)
|
|
teacher.retailer_id = params[:retailer_id]
|
|
if !teacher.joined_retailer_at
|
|
teacher.joined_retailer_at = Time.now
|
|
end
|
|
end
|
|
|
|
# How to validate:
|
|
teacher.validate_introduction = !!params[:validate_introduction]
|
|
teacher.validate_pricing = !!params[:validate_pricing]
|
|
|
|
|
|
initial_save = teacher.save
|
|
|
|
teacher.validate_basics = !!params[:validate_basics]
|
|
|
|
if initial_save
|
|
# Many-to-many relations:
|
|
if params.key?(:genres)
|
|
genres = params[:genres]
|
|
genres = [] if genres.nil?
|
|
teacher.genres.clear
|
|
genres.each do |genre_id|
|
|
teacher.genres << Genre.find(genre_id)
|
|
end
|
|
end
|
|
if params.key?(:instruments)
|
|
instruments = params[:instruments]
|
|
instruments = [] if instruments.nil?
|
|
teacher.instruments.clear
|
|
instruments.each do |instrument_id|
|
|
teacher.instruments << Instrument.find(instrument_id)
|
|
end
|
|
end
|
|
if params.key?(:subjects)
|
|
subjects = params[:subjects]
|
|
subjects = [] if subjects.nil?
|
|
teacher.subjects.clear
|
|
subjects.each do |subject_id|
|
|
teacher.subjects << Subject.find(subject_id)
|
|
end
|
|
end
|
|
if params.key?(:languages)
|
|
languages = params[:languages]
|
|
languages = [] if languages.nil?
|
|
teacher.languages.clear
|
|
languages.each do |language_id|
|
|
teacher.languages << Language.find(language_id)
|
|
end
|
|
end
|
|
|
|
|
|
# Experience:
|
|
[:teaching, :education, :award].each do |experience_type|
|
|
key = "experiences_#{experience_type}".to_sym
|
|
if params.key?(key)
|
|
list = params[key]
|
|
list = [] if list.nil?
|
|
experiences = list.collect do |exp|
|
|
TeacherExperience.new(
|
|
name: exp[:name],
|
|
experience_type: experience_type,
|
|
organization: exp[:organization],
|
|
start_year: exp[:start_year],
|
|
end_year: exp[:end_year]
|
|
)
|
|
end # collect
|
|
|
|
# we blindly destroy/recreate on every resubmit
|
|
previous = teacher.send("#{key.to_s}")
|
|
previous.destroy_all
|
|
|
|
# Dynamically call the appropriate method (just setting the
|
|
# value doesn't result in the behavior we need)
|
|
teacher.send("#{key.to_s}=", experiences)
|
|
end # if
|
|
end # do
|
|
end
|
|
|
|
|
|
return teacher
|
|
end
|
|
|
|
def booking_price(lesson_length, single)
|
|
price = nil
|
|
if single
|
|
price = self["price_per_lesson_#{lesson_length}_cents"]
|
|
else
|
|
price = self["price_per_month_#{lesson_length}_cents"]
|
|
end
|
|
|
|
if !price.nil?
|
|
price / 100.0
|
|
else
|
|
price
|
|
end
|
|
end
|
|
|
|
def booking_price_table
|
|
table = {single:[], monthly:[]}
|
|
durations_allowed = []
|
|
[30, 45, 60, 90, 120].each do |i|
|
|
durations_allowed << i if self["lesson_duration_#{i}"]
|
|
end
|
|
|
|
durations_allowed.each do |i|
|
|
if self["price_per_lesson_#{i}_cents"]
|
|
table[:single] << {minutes: i, price: self["price_per_lesson_#{i}_cents"]}
|
|
end
|
|
if self["price_per_month_#{i}_cents"]
|
|
table[:monthly] << {minutes: i, price: self["price_per_month_#{i}_cents"]}
|
|
end
|
|
end
|
|
table
|
|
end
|
|
|
|
def offer_pricing
|
|
unless prices_per_lesson.present? || prices_per_month.present?
|
|
errors.add(:offer_pricing, "Must choose to price per lesson or per month")
|
|
end
|
|
end
|
|
|
|
def offer_duration
|
|
unless lesson_duration_30.present? || lesson_duration_45.present? || lesson_duration_60.present? || lesson_duration_90.present? || lesson_duration_120.present?
|
|
errors.add(:offer_duration, "Must offer at least one duration")
|
|
end
|
|
end
|
|
|
|
def teaches_ages
|
|
if teaches_age_lower > 0 && teaches_age_upper > 0 && (teaches_age_upper < teaches_age_lower)
|
|
errors.add(:ages_taught, "Age range is backwards")
|
|
end
|
|
end
|
|
|
|
def recent_reviews
|
|
reviews.order('created_at desc').limit(20)
|
|
end
|
|
|
|
def mark_background_checked(time)
|
|
self.background_check_at = time
|
|
self.save!
|
|
end
|
|
|
|
def mark_session_ready
|
|
self.ready_for_session_at = Time.now
|
|
self.save!
|
|
end
|
|
|
|
def mark_not_session_ready
|
|
self.ready_for_session_at = nil
|
|
self.save!
|
|
end
|
|
|
|
def mark_top_rated
|
|
self.top_rated = true
|
|
self.save!
|
|
end
|
|
|
|
def mark_not_top_rated
|
|
self.top_rated = false
|
|
self.save!
|
|
end
|
|
|
|
def has_experiences_teaching?
|
|
experiences_teaching.count > 0
|
|
end
|
|
|
|
def has_experiences_education?
|
|
experiences_education.count > 0
|
|
end
|
|
|
|
def has_experiences_award?
|
|
experiences_award.count > 0
|
|
end
|
|
|
|
def has_stripe_billing?
|
|
user.has_stripe_connect?
|
|
end
|
|
|
|
def has_instruments_or_subject?
|
|
instruments.count > 0 || subjects.count > 0
|
|
end
|
|
|
|
def has_genres?
|
|
genres.count > 0
|
|
end
|
|
|
|
def has_languages?
|
|
languages.count > 0
|
|
end
|
|
|
|
def teaches_ages_specified?
|
|
(!teaches_age_lower.nil? && teaches_age_lower > 0) || (!teaches_age_upper.nil? && teaches_age_upper > 0)
|
|
end
|
|
|
|
def teaching_level_specified?
|
|
teaches_beginner || teaches_intermediate || teaches_advanced
|
|
end
|
|
|
|
def has_pricing_specified?
|
|
specified = false
|
|
durations_allowed = []
|
|
[30, 45, 60, 90, 120].each do |i|
|
|
durations_allowed << i if self["lesson_duration_#{i}"]
|
|
end
|
|
|
|
durations_allowed.each do |i|
|
|
if self["price_per_lesson_#{i}_cents"] || self["price_per_month_#{i}_cents"]
|
|
specified = true
|
|
break
|
|
end
|
|
end
|
|
specified
|
|
end
|
|
|
|
def has_name_specified?
|
|
!user.anonymous?
|
|
end
|
|
|
|
def stripe_account_id
|
|
user.stripe_auth.uid if user.has_stripe_connect?
|
|
end
|
|
|
|
## !!!! this is only valid for tests
|
|
def stripe_account_id=(new_acct_id)
|
|
user.stripe_account_id = new_acct_id
|
|
end
|
|
|
|
# how complete is their profile?
|
|
def pct_complete
|
|
@part_complete ||= {
|
|
name_specified: has_name_specified?,
|
|
experiences_teaching: has_experiences_teaching?,
|
|
experiences_education: has_experiences_education?,
|
|
experiences_award: has_experiences_award?,
|
|
has_stripe_account: has_stripe_billing?,
|
|
has_teacher_bio: !biography.nil?,
|
|
intro_video: !introductory_video.nil?,
|
|
years_teaching: years_teaching > 0,
|
|
years_playing: years_playing > 0,
|
|
instruments_or_subject: has_instruments_or_subject?,
|
|
genres: genres.count > 0,
|
|
languages: languages.count > 0,
|
|
teaches_ages_specified: teaches_ages_specified?,
|
|
teaching_level_specified: teaching_level_specified?,
|
|
has_pricing_specified: has_pricing_specified?
|
|
}
|
|
|
|
done = 0
|
|
@part_complete.each do |k, v|
|
|
if v
|
|
done += 1
|
|
end
|
|
end
|
|
|
|
complete = 100.0 * done.to_f / @part_complete.length.to_f
|
|
|
|
@part_complete[:pct] = complete.round
|
|
@part_complete
|
|
end
|
|
|
|
def teaches
|
|
if instruments.length == 0
|
|
return ''
|
|
elsif instruments.length == 2
|
|
return 'Teaches ' + instruments[0].description + ' and ' + instruments[1].description
|
|
else
|
|
return 'Teaches ' + instruments.map { |i| i.description }.join(', ')
|
|
end
|
|
end
|
|
end
|
|
end
|