require "devise" # A person enrolled in the intervention. class Participant < ActiveRecord::Base include ThinkFeelDoEngine::Concerns::ValidatePassword devise :database_authenticatable, :recoverable, :trackable, :validatable, :timeoutable, timeout_in: 20.minutes has_many :memberships, dependent: :destroy has_one :active_membership, -> { active }, class_name: "Membership", foreign_key: :participant_id, dependent: :destroy, inverse_of: :active_participant has_many :groups, through: :memberships has_one :active_group, through: :active_membership has_many :activities, -> { includes :activity_type }, dependent: :destroy has_many :planned_activities, -> { includes :activity_type } has_many :awake_periods, dependent: :destroy has_many :activity_types, dependent: :destroy has_many :emotions, dependent: :destroy, foreign_key: :creator_id has_many :emotional_ratings, -> { includes :emotion }, dependent: :destroy has_many :moods, dependent: :destroy has_many :thoughts, -> { includes :pattern }, dependent: :destroy has_many :sent_messages, class_name: "Message", as: :sender has_many :messages, as: :sender, dependent: :destroy has_many :received_messages, -> { includes message: :sender }, class_name: "DeliveredMessage", as: :recipient, dependent: :destroy has_many :addressed_messages, class_name: "Message", as: :recipient, dependent: :destroy has_many :phq_assessments, dependent: :destroy has_many :wai_assessments, dependent: :destroy has_many :participant_tokens, dependent: :destroy has_one :participant_status, class_name: "BitPlayer::ParticipantStatus", dependent: :destroy has_one :coach_assignment, dependent: :destroy has_one :coach, class_name: "User", through: :coach_assignment has_many :participant_login_events, dependent: :destroy has_many :media_access_events, dependent: :destroy has_many :events, class_name: "EventCapture::Event", foreign_key: :participant_id, dependent: :destroy has_many :click_events, -> { where(kind: "click") }, class_name: "EventCapture::Event", foreign_key: :participant_id delegate :end_date, to: :active_membership, prefix: true, allow_nil: true accepts_nested_attributes_for :coach_assignment def self.active joins(:memberships).merge(Membership.active) end def self.inactive joins(:memberships).merge(Membership.inactive) end scope :stepped, lambda { joins(:memberships) .where( Arel::Table.new(:memberships)[:stepped_on] .not_eq(nil) ) } scope :not_stepped, lambda { joins(:memberships) .where( Arel::Table.new(:memberships)[:stepped_on] .eq(nil) ) } scope :not_moderator, lambda { where(is_admin: false) } def is_not_allowed_in_site # participant not set to is_complete (hence withdrawal or termination) # and who have no active memberships active_membership.nil? && !memberships.where(is_complete: true).exists? end def active_group_is_social? active_group && active_group.arm.social? end def duration_of_last_session latest_action_at - current_sign_in_at end def latest_action_at events .order(recorded_at: :desc) .first .try(:recorded_at) end def populate_emotions emotions_array = [ "Anxious", "Enthusiastic", "Grateful", "Happy", "Irritable", "Upset", "Sad", "Guilty", "Calm", "Concentrated", "Relaxed" ] emotions_array.each do |e| emotions.find_or_create_by(name: e) end emotions end def unfinished_awake_periods join_sql = <<-SQL LEFT JOIN activities ON activities.participant_id = awake_periods.participant_id AND activities.start_time = awake_periods.start_time SQL awake_periods.joins(join_sql).where("activities.start_time IS NULL") end def most_recent_unfinished_awake_period unfinished_awake_periods.order("awake_periods.start_time DESC").first end def build_data_record(association, attributes) send(association).build(attributes) end def build_phq_assessment(attributes) phq_assessments.build(attributes) end def current_group active_membership.group end def fetch_data_record(association, id) send(association).find(id) end def count_unread_messages received_messages.where(is_read: false).count end def count_all_incomplete(tool) @count_all_incomplete ||= {} @count_all_incomplete[tool.id] ||= ( if tool.is_a?(Tools::Messages) count_unread_messages else content_module_ids = tool.content_modules.where(is_viz: false) if content_module_ids active_membership.incomplete_tasks .for_content_module_ids(content_module_ids) .count else false end end ) end def learning_tasks(content_modules) active_membership.task_statuses .for_content_module_ids(content_modules.map(&:id)) end def stepping_suggestion data = phq_assessments.map do |x| [x.release_date, x] if x.number_answered > 0 end assessment_data = Hash[data] PhqStepping.new(assessment_data, active_membership.start_date) end def navigation_status participant_status || build_participant_status end def recent_accomplished_activities recent_activities.accomplished end def recent_activities activities.during(recent_period[:start_time], recent_period[:end_time]) end def recent_pleasurable_activities recent_activities.pleasurable end def flag_inactivity participant_login_events .order(:created_at) .last .try(:update_attribute, :inactive_log_out, true) end # Checks whether the user session has expired based on configured time. # Overridden devise method. def timedout?(last_access) timedout = super flag_inactivity if timedout timedout end def unplanned_activities UnplannedActivities.new(self) end def contact_status_enum %w(sms email) end def notify_by_email? "email" == contact_preference end def notify_by_sms? "sms" == contact_preference || "phone" == contact_preference end def average_rating(array) array.reduce(0) { |a, e| a.to_f + e[0] } / array.size end def positive_emotions(emotion_array) emotions = emotion_array.collect do |emotion| if emotion.is_positive [emotion.rating, emotion.created_at, emotion.name] end end emotions.compact end def negative_emotions(emotion_array) emotions = emotion_array.collect do |emotion| unless emotion.is_positive [emotion.rating, emotion.created_at, emotion.name] end end emotions.compact end def most_recent_membership memberships.order(end_date: :desc).first end def emotional_rating_daily_averages averaged_ratings = [] daily_ratings = emotional_ratings.group_by(&:created_at) # rubocop:disable all daily_ratings.each do |day, emotion_array| # rubocop:enable all positive_ratings = positive_emotions(emotion_array) if positive_ratings.size > 0 daily_positive = { day: day, intensity: average_rating(positive_ratings), is_positive: true, drill_down: positive_ratings, data_type: "Emotion" } averaged_ratings << daily_positive end negative_ratings = negative_emotions(emotion_array) if negative_ratings.size > 0 daily_negative = { day: day, intensity: average_rating(negative_ratings), is_positive: false, drill_down: negative_ratings, data_type: "Emotion" } averaged_ratings << daily_negative end end averaged_ratings end def in_study? if active_membership.start_date <= Date.today && active_membership.end_date >= Date.today true else false end end def mood_rating_daily_averages averaged_ratings = [] daily_ratings = moods.group_by(&:created_at) # rubocop:disable all daily_ratings.each do |day, moods_array| # rubocop:enable all ratings = moods_array.collect do |mood| [mood.rating, mood.created_at].compact end if ratings.size > 0 averaged_ratings << { day: day, intensity: average_rating(ratings), is_positive: true, drill_down: ratings, data_type: "Mood" } end end averaged_ratings end private def recent_awake_period @recent_awake_period ||= awake_periods.order("start_time").last end def recent_period @recent_period ||= ( # when no awake period return an empty set to allow chaining now = Time.new start_time = recent_awake_period.try(:start_time) || now end_time = recent_awake_period.try(:end_time) || now { start_time: start_time, end_time: end_time } ) end end