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 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 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') if user && 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 # 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.website = params[:website] if params.key?(:website) teacher.biography = params[:biography] if params.key?(:biography) 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.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 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 self.background_check_at = Time.now self.save! end def mark_session_ready self.ready_for_session_at = Time.now 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