module JamRuby class ActiveMusicSession < ActiveRecord::Base @@log = Logging.logger[ActiveMusicSession] self.primary_key = 'id' self.table_name = 'active_music_sessions' attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome, :jam_track_id belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id" belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :foreign_key => "jam_track_id", :inverse_of => :playing_sessions belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "jam_track_initiator_id" belongs_to :backing_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "backing_track_initiator_id" belongs_to :metronome_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "metronome_initiator_id" has_one :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => 'music_session_id' has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id' belongs_to :creator, :class_name => 'JamRuby::User', :foreign_key => :user_id has_many :connections, :class_name => "JamRuby::Connection", foreign_key: :music_session_id has_many :users, :through => :connections, :class_name => "JamRuby::User" has_many :recordings, :class_name => "JamRuby::Recording", :inverse_of => :music_session, foreign_key: :music_session_id has_many :chats, :class_name => "JamRuby::ChatMessages", :foreign_key => "session_id" validates :creator, :presence => true validate :creator_is_musician validate :validate_opening_recording, :if => :opening_recording validate :validate_opening_jam_track, :if => :opening_jam_track validate :validate_opening_backing_track, :if => :opening_backing_track # not sure if this is helpful since if one opens, it always stays open validate :validate_opening_metronome, :if => :opening_metronome after_create :started_session after_destroy do |obj| JamRuby::MusicSession.removed_music_session(obj.id) end #default_scope :select => "*, 0 as score" def attributes super.merge('max_score' => self.max_score) end def max_score nil unless has_attribute?(:max_score) read_attribute(:max_score).to_i end before_create :create_uuid def create_uuid #self.id = SecureRandom.uuid end def before_destroy feed = Feed.find_by_music_session_id(self.id) unless feed.nil? feed.active = false feed.save end self.mount.destroy if self.mount end def creator_is_musician unless creator.musician? errors.add(:creator, ValidationMessages::MUST_BE_A_MUSICIAN) end end def validate_opening_recording unless claimed_recording_id_was.nil? errors.add(:claimed_recording, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS) end if is_jam_track_open? errors.add(:claimed_recording, ValidationMessages::JAM_TRACK_ALREADY_OPEN) end if is_backing_track_open? errors.add(:claimed_recording, ValidationMessages::BACKING_TRACK_ALREADY_OPEN) end if is_metronome_open? errors.add(:claimed_recording, ValidationMessages::METRONOME_ALREADY_OPEN) end end def validate_opening_jam_track validate_other_audio(:jam_track) end def validate_opening_backing_track validate_other_audio(:backing_track) end def validate_opening_metronome validate_other_audio(:metronome) end def validate_other_audio(error_key) # validate that there is no backing track already open in this session if backing_track_path_was.present? errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN) end # validate that there is no jam track already open in this session if jam_track_id_was.present? errors.add(error_key, ValidationMessages::JAM_TRACK_ALREADY_OPEN) end # validate that there is no recording being made if is_recording? errors.add(error_key, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS) end # validate that there is no recording being played back to the session if is_playing_recording? errors.add(error_key, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS) end end # returns an array of client_id's that are in this session # if as_musician is nil, all connections in the session ,regardless if it's a musician or not or not # you can also exclude a client_id from the returned set by setting exclude_client_id def get_connection_ids(options = {}) as_musician = options[:as_musician] exclude_client_id = options[:exclude_client_id] where = { :music_session_id => self.id } where[:as_musician] = as_musician unless as_musician.nil? exclude = "client_id != '#{exclude_client_id}'"unless exclude_client_id.nil? Connection.select(:client_id).where(where).where(exclude).map(&:client_id) end # This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true # If so, then it's an OR condition. If both are false, you can get sessions with anyone. def self.index(current_user, options = {}) participants = options[:participants] genres = options[:genres] keyword = options[:keyword] friends_only = options[:friends_only].nil? ? false : options[:friends_only] my_bands_only = options[:my_bands_only].nil? ? false : options[:my_bands_only] as_musician = options[:as_musician].nil? ? true : options[:as_musician] query = ActiveMusicSession .joins( %Q{ INNER JOIN music_sessions ON active_music_sessions.id = music_sessions.id } ) .joins( %Q{ INNER JOIN connections ON active_music_sessions.id = connections.music_session_id } ) .joins( %Q{ LEFT OUTER JOIN friendships ON connections.user_id = friendships.user_id AND friendships.friend_id = '#{current_user.id}' } ) .joins( %Q{ LEFT OUTER JOIN invitations ON invitations.music_session_id = active_music_sessions.id AND invitations.receiver_id = '#{current_user.id}' } ) .group( %Q{ active_music_sessions.id } ) .order( %Q{ SUM(CASE WHEN invitations.id IS NULL THEN 0 ELSE 1 END) DESC, SUM(CASE WHEN friendships.user_id IS NULL THEN 0 ELSE 1 END) DESC, active_music_sessions.created_at DESC } ) query = Search.scope_schools_together_sessions(query, current_user) if as_musician query = query.where( %Q{ musician_access = true OR invitations.id IS NOT NULL } ) else # if you are trying to join the session as a fan/listener, # we have to have a mount, fan_access has to be true, and we have to allow for the reload of icecast to have taken effect query = query.joins('INNER JOIN icecast_mounts ON icecast_mounts.music_session_id = active_music_sessions.id INNER JOIN icecast_servers ON icecast_mounts.icecast_server_id = icecast_servers.id') query = query.where('music_sessions.fan_access = true') query = query.where("(active_music_sessions.created_at < icecast_servers.config_updated_at)") end query = query.where("music_sessions.description like '%#{keyword}%'") unless keyword.nil? query = query.where("connections.user_id" => participants.split(',')) unless participants.nil? query = query.where("music_sessions.genre_id in (?)", genres) unless genres.nil? if my_bands_only query = query.joins( %Q{ LEFT OUTER JOIN bands_musicians ON bands_musicians.user_id = '#{current_user.id}' } ) end if my_bands_only || friends_only query = query.where( %Q{ #{friends_only ? "friendships.user_id IS NOT NULL" : "false"} OR #{my_bands_only ? "bands_musicians.band_id = music_sessions.band_id" : "false"} } ) end return query end # all sessions that are private and active, yet I can see def self.friend_active_index(user, options) session_id = options[:session_id] genre = options[:genre] lang = options[:lang] keyword = options[:keyword] offset = options[:offset] limit = options[:limit] query = MusicSession.select('music_sessions.*') query = query.joins("INNER JOIN active_music_sessions ON music_sessions.id = active_music_sessions.id") # one flaw in the join below is that we only consider if the creator of the session has asked you to be your friend, but you have not accepted. While this means you may always see a session by someone you haven't friended, the goal is to match up new users more quickly query = query.joins( %Q{ LEFT OUTER JOIN rsvp_requests ON rsvp_requests.music_session_id = active_music_sessions.id and rsvp_requests.user_id = '#{user.id}' AND rsvp_requests.chosen = true LEFT OUTER JOIN invitations ON active_music_sessions.id = invitations.music_session_id AND invitations.receiver_id = '#{user.id}' LEFT OUTER JOIN friendships ON active_music_sessions.user_id = friendships.user_id AND friendships.friend_id = '#{user.id}' LEFT OUTER JOIN friendships as friendships_2 ON active_music_sessions.user_id = friendships_2.friend_id AND friendships_2.user_id = '#{user.id}' } ) # keep only rsvp/invitation/friend results. Nice tailored active list now! query = query.where("rsvp_requests.id IS NOT NULL OR invitations.id IS NOT NULL or active_music_sessions.user_id = '#{user.id}' OR (friendships.id IS NOT NULL AND friendships_2.id IS NOT NULL)") # if not specified, default offset to 0 offset ||= 0 offset = offset.to_i # if not specified, default limit to 20 limit ||= 20 limit = limit.to_i query = query.offset(offset) query = query.limit(limit) query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank? query = query.where('music_sessions.language = ?', lang) unless lang.blank? query = query.where('music_sessions.id = ?', session_id) unless session_id.blank? query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank? query = query.group("music_sessions.id, active_music_sessions.created_at") query = query.order("active_music_sessions.created_at DESC") query end # all sessions that are private and active, yet I can see def self.public_index(user, options) session_id = options[:session_id] genre = options[:genre] lang = options[:lang] keyword = options[:keyword] offset = options[:offset] limit = options[:limit] query = MusicSession.select('music_sessions.*') query = query.joins("INNER JOIN active_music_sessions ON music_sessions.id = active_music_sessions.id") query = query.where("musician_access = TRUE") query = Search.scope_schools_together_sessions(query, user) # if not specified, default offset to 0 offset ||= 0 offset = offset.to_i # if not specified, default limit to 20 limit ||= 20 limit = limit.to_i query = query.offset(offset) query = query.limit(limit) query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank? query = query.where('music_sessions.language = ?', lang) unless lang.blank? query = query.where('music_sessions.id = ?', session_id) unless session_id.blank? query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank? query = query.group("music_sessions.id, active_music_sessions.created_at") query = query.order("active_music_sessions.created_at DESC") query end # This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true # If so, then it's an OR condition. If both are false, you can get sessions with anyone. # note, this is mostly the same as above but includes paging through the result and and scores. # thus it needs the client_id... def self.nindex(current_user, options = {}) client_id = options[:client_id] participants = options[:participants] genres = options[:genres] keyword = options[:keyword] friends_only = options[:friends_only].nil? ? false : options[:friends_only] my_bands_only = options[:my_bands_only].nil? ? false : options[:my_bands_only] as_musician = options[:as_musician].nil? ? true : options[:as_musician] offset = options[:offset] limit = options[:limit] connection = Connection.where(client_id: client_id).first! locidispid = connection.locidispid query = ActiveMusicSession .select("active_music_sessions.*, max(coalesce(current_scores.score, 1000)) as max_score") # 1000 is higher than the allowed max of 999 .joins( %Q{ INNER JOIN music_sessions ON active_music_sessions.id = music_sessions.id } ) .joins( %Q{ INNER JOIN connections ON active_music_sessions.id = connections.music_session_id } ) .joins( %Q{ LEFT OUTER JOIN current_scores ON current_scores.alocidispid = connections.locidispid AND current_scores.blocidispid = #{locidispid} } ) .joins( %Q{ LEFT OUTER JOIN friendships ON connections.user_id = friendships.user_id AND friendships.friend_id = '#{current_user.id}' } ) .joins( %Q{ LEFT OUTER JOIN invitations ON invitations.music_session_id = active_music_sessions.id AND invitations.receiver_id = '#{current_user.id}' } ) .group( %Q{ active_music_sessions.id } ) .order( %Q{ SUM(CASE WHEN invitations.id IS NULL THEN 0 ELSE 1 END) DESC, SUM(CASE WHEN friendships.user_id IS NULL THEN 0 ELSE 1 END) DESC, active_music_sessions.created_at DESC } ) query = Search.scope_schools_together_sessions(query, current_user) query = query.offset(offset) if offset query = query.limit(limit) if limit if as_musician query = query.where( %Q{ musician_access = true OR music_sessions.user_id = '#{current_user.id}' OR invitations.id IS NOT NULL } ) else # if you are trying to join the session as a fan/listener, # we have to have a mount, fan_access has to be true, and we have to allow for the reload of icecast to have taken effect query = query.joins('INNER JOIN icecast_mounts ON icecast_mounts.music_session_id = active_music_sessions.id INNER JOIN icecast_servers ON icecast_mounts.icecast_server_id = icecast_servers.id') query = query.where('music_sessions.fan_access = true') query = query.where("(active_music_sessions.created_at < icecast_servers.config_updated_at)") end query = query.where("music_sessions.description like '%#{keyword}%'") unless keyword.nil? query = query.where("connections.user_id" => participants.split(',')) unless participants.nil? query = query.where("music_sessions.genre_id in (?)", genres) unless genres.nil? if my_bands_only query = query.joins( %Q{ LEFT OUTER JOIN bands_musicians ON bands_musicians.user_id = '#{current_user.id}' } ) end if my_bands_only || friends_only query = query.where( %Q{ #{friends_only ? "friendships.user_id IS NOT NULL" : "false"} OR #{my_bands_only ? "bands_musicians.band_id = music_sessions.band_id" : "false"} } ) end return query end # initialize the two temporary tables we use to drive ams_index and ams_users def self.ams_init(current_user, options = {}) my_locidispid = current_user.last_jam_locidispid # 13 is an average audio gear value we use if they have not qualified any gear my_audio_latency = current_user.last_jam_audio_latency || 13 locidispid_expr = my_locidispid ? "#{my_locidispid}::bigint" : '0::bigint' self.connection.execute("select ams_index('#{current_user.id}'::varchar, #{locidispid_expr}, #{my_audio_latency}::integer)").check end # Generate a list of music sessions (that are active) filtered by genre, language, keyword, and sorted # (and tagged) by rsvp'd (1st), invited (2nd), and musician can join (3rd). within a group tagged the # same, sorted by score. date seems irrelevant as these are active sessions. ams_init must be called # first. def self.ams_query(current_user, options = {}) session_id = options[:session_id] client_id = options[:client_id] genre = options[:genre] lang = options[:lang] keyword = options[:keyword] offset = options[:offset] limit = options[:limit] day = options[:day] timezone_offset = options[:timezone_offset] query = MusicSession .select('music_sessions.*') # this is not really needed when ams_music_session_tmp is joined # unless there is something specific we need out of active_music_sessions # query = query.joins( # %Q{ # INNER JOIN # active_music_sessions # ON # active_music_sessions.id = music_sessions.id # } # ) # .select('1::integer as tag, 15::integer as latency') # integrate ams_music_session_tmp into the processing # then we can join ams_music_session_tmp and not join active_music_sessions query = query.joins( %Q{ INNER JOIN ams_music_session_tmp ON ams_music_session_tmp.music_session_id = music_sessions.id } ) .select('ams_music_session_tmp.tag, ams_music_session_tmp.latency') query = query.order( %Q{ tag, latency, music_sessions.id } ) .group( %Q{ tag, latency, music_sessions.id } ) # if not specified, default offset to 0 offset ||= 0 offset = offset.to_i # if not specified, default limit to 20 limit ||= 20 limit = limit.to_i query = query.offset(offset) query = query.limit(limit) query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank? query = query.where('music_sessions.language = ?', lang) unless lang.blank? query = query.where('music_sessions.id = ?', session_id) unless session_id.blank? query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank? if !day.blank? && !timezone_offset.blank? begin day = Date.parse(day) next_day = day + 1 timezone_offset = timezone_offset.to_i if timezone_offset > 0 timezone_offset = "+#{timezone_offset}" end query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}' AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'") rescue Exception => e # do nothing. bad date probably @@log.warn("unable to parse day=#{day}, timezone_offset=#{timezone_offset}, e=#{e}") end end return query end # returns the set of users in a music_sessions and the music_session they are in and their latency. # ams_init must be called first. # user.audio_latency / 2 , + other_user.audio_latency of them / 2, + network latency /2 def self.ams_users return User.select('users.*, ams_users_tmp.music_session_id, ams_users_tmp.full_score, ams_users_tmp.audio_latency, ams_users_tmp.internet_score') .joins( %Q{ INNER JOIN ams_users_tmp ON ams_users_tmp.user_id = users.id } ) .order('ams_users_tmp.music_session_id, ams_users_tmp.user_id') end # NOTE: unused anymore! # # wrap me in a transaction! # note that these queries must be actualized before the end of the transaction # else the temporary tables created by sms_init will be gone. def self.ams_index(current_user, params) ActiveMusicSession.ams_init(current_user, params) music_sessions = ActiveMusicSession.ams_query(current_user, params).all music_session_users = ActiveMusicSession.ams_users.all user_scores = {} music_session_users.each do |user| user_scores[user.id] = {full_score: user.full_score, audio_latency: user.audio_latency, internet_score: user.internet_score} end [music_sessions, user_scores] end def self.participant_create(user, music_session_id, client_id, as_musician, tracks, audio_latency, client_role = nil, parent_client_id = nil, video_sources=nil) music_session = MusicSession.find(music_session_id) # USERS ARE ALREADY IN SESSION if music_session.active_music_session connection = nil active_music_session = music_session.active_music_session ActiveRecord::Base.transaction do active_music_session.with_lock do # VRFS-1297 active_music_session.tick_track_changes Notification.send_tracks_changed(active_music_session) # VRFS-3986 connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, client_role, parent_client_id, video_sources) if connection.errors.any? # rollback the transaction to make sure nothing is disturbed in the database raise ActiveRecord::Rollback end end end unless connection.errors.any? user.update_progression_field(:first_music_session_at) MusicSessionUserHistory.save(music_session_id, user.id, client_id, tracks) if as_musician # send to session participants Notification.send_session_join(active_music_session, connection, user) # send "musician joined session" notification only if it's not a band session since there will be a "band joined session" notification if music_session.band.nil? Notification.send_musician_session_join(music_session, user) end end end connection # FIRST USER TO JOIN SESSION else return_value = nil time = Benchmark.realtime do ActiveRecord::Base.transaction do # we need to lock the icecast server in this transaction for writing, to make sure thath IcecastConfigWriter # doesn't dumpXML as we are changing the server's configuraion icecast_server = IcecastServer.find_best_server_for_user(user) if music_session.fan_access icecast_server.lock! if icecast_server music_session.running_recordings.each do |recording| recording.stop end # check if we are connected to rabbitmq active_music_session = ActiveMusicSession.new active_music_session.id = music_session.id # copy the .id from music_session to active_music_session active_music_session.creator = user active_music_session.school_id = user.school_id active_music_session.is_platform_instructor = user.is_platform_instructor if music_session.fan_access # create an icecast mount since regular users can listen in to the broadcast active_music_session.mount = IcecastMount.build_session_mount(music_session, active_music_session, icecast_server) end active_music_session.save unless active_music_session.errors.any? music_session.started_at = active_music_session.created_at music_session.save(:validate => false) # save session parameters for next session User.save_session_settings(user, music_session) # auto-join this user into the newly created session as_musician = true connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, client_role, parent_client_id, video_sources) unless connection.errors.any? user.update_progression_field(:first_music_session_at) MusicSessionUserHistory.save(active_music_session.id, user.id, client_id, tracks) Notification.send_session_join(active_music_session, connection, user) # only send this notification if it's a band session unless music_session.band.nil? Notification.send_band_session_join(music_session, music_session.band) else Notification.send_musician_session_join(music_session, user) end return_value = connection else return_value = connection # rollback the transaction to make sure nothing is disturbed in the database raise ActiveRecord::Rollback end else return_value = active_music_session # rollback the transaction to make sure nothing is disturbed in the database raise ActiveRecord::Rollback end end end if time > 2 Logging.logger[self].warn "creating a music session took #{time*1000} milliseconds" end return_value end end # Verifies that the specified user can delete this music session def can_delete? user # the creator can delete self.creator == user end def access? user music_session.part_of_session? user end def most_recent_recording recordings.where(:music_session_id => self.id).order('created_at desc').limit(1).first end def is_jam_track_open? !self.jam_track.nil? end def is_backing_track_open? self.backing_track_path.present? end def is_metronome_open? self.metronome_active.present? end # is this music session currently recording? def is_recording? recordings.where(:duration => nil).count > 0 end def is_playing_recording? !self.claimed_recording.nil? end def recording recordings.where(:duration => nil).first end # stops any active recording def stop_recording current_recording = self.recording current_recording.stop unless current_recording.nil? end def claimed_recording_start(owner, claimed_recording) self.claimed_recording = claimed_recording self.claimed_recording_initiator = owner self.opening_recording = true self.save self.opening_recording = false end def claimed_recording_stop self.claimed_recording = nil self.claimed_recording_initiator = nil self.save end def friends_can_join music_session.friends_can_join end def invitations music_session.invitations end def invited_musicians music_session.invited_musicians end def join_requests music_session.join_requests end def fan_invitations music_session.fan_invitations end def to_s description end def is_lesson_member?(user) music_session.is_lesson_member?(user) end def musician_access music_session.musician_access end def fan_access music_session.fan_access end def description music_session.description end def name music_session.name end def genre music_session.genre end def fan_chat music_session.fan_chat end def band music_session.band end def approval_required music_session.approval_required end def music_notations music_session.music_notations end def music_session_id_int music_session.music_session_id_int end # Verifies that the specified user can join this music session def can_join? user, as_musician music_session.can_join? user, as_musician end # Verifies that the specified user can see this music session def can_see? user music_session.can_see? user end def tick_track_changes self.track_changes_counter += 1 self.save!(:validate => false) end def connected_participant_count Connection.where(:music_session_id => self.id, :aasm_state => Connection::CONNECT_STATE.to_s, :as_musician => true) .count end def started_session raise "active_music_sessions.id must be set by caller" unless self.id # associate this active_music_session with the music_session formally session = MusicSession.find(self.id) session.active_music_session = self self.music_session = session # needed because of the Google Analytics below in some test cases session.scheduled_start = self.created_at unless session.scheduled_start session.save! feed = Feed.find_by_music_session_id(self.id) # this should never be hit since the feed entry is created when the music_session record is created if feed.nil? feed = Feed.new feed.music_session_id = self.id end feed.school_id = self.school_id feed.is_platform_instructor = self.is_platform_instructor feed.active = true feed.save #GoogleAnalyticsEvent.track_session_duration(self) #GoogleAnalyticsEvent.track_band_real_session(self) end def open_jam_track(user, jam_track) self.jam_track = jam_track self.jam_track_initiator = user self.opening_jam_track = true self.save self.opening_jam_track = false JamTrackSession.create_session(jam_track, user, self.music_session) if jam_track && user #self.tick_track_changes end def close_jam_track self.jam_track = nil self.jam_track_initiator = nil self.save end # @param backing_track_path is a relative path: def open_backing_track(user, backing_track_path) self.backing_track_path = backing_track_path self.backing_track_initiator = user self.opening_backing_track = true self.save self.opening_backing_track = false end def close_backing_track self.backing_track_path = nil self.backing_track_initiator = nil self.save end def open_metronome(user) self.metronome_active = true self.metronome_initiator = user self.opening_metronome = true self.save self.opening_metronome = false end def close_metronome self.metronome_active = false self.metronome_initiator = nil self.save end def play_time_remaining(user) rules = SubscriptionDefinitions.rules(user.subscription_plan_code) play_time_per_session = rules[:play_time_per_session] if play_time_per_session.nil? nil else (play_time_per_session * 3600) - MusicSessionUserHistory.where(music_session_id: self.id).where(user_id: user.id).sum("extract('epoch' from (COALESCE(session_removed_at, NOW()) - created_at))") end end def self.sync(session_history) music_session = MusicSession.find_by_id(session_history.id) if music_session.nil? music_session = MusicSession.new music_session.id = session_history.id end music_session.user_id = session_history.creator.id music_session.band_id = session_history.band.id unless session_history.band.nil? session_history.save! end def self.stats stats = {} result = ActiveMusicSession.select('count(distinct(id)) AS total, count(distinct(jam_track_initiator_id)) as jam_track_count, count(distinct(backing_track_initiator_id)) as backing_track_count, count(distinct(metronome_initiator_id)) as metronome_count, count(distinct(claimed_recording_initiator_id)) as recording_count')[0] stats['count'] = result['total'].to_i stats['jam_track_count'] = result['jam_track_count'].to_i stats['backing_track_count'] = result['backing_track_count'].to_i stats['metronome_count'] = result['metronome_count'].to_i stats['recording_count'] = result['recording_count'].to_i stats end def lesson_session music_session.lesson_session end end end