fuyi
1/21/2015 - 2:36 PM

gistfile1.rb

class Competition < ActiveRecord::Base

  PERCENTATE = 0.1
  ON_FIRE_COUNT = 3

  scope :on_going, -> { where('start_time < ? AND end_time > ?', Time.now(), Time.now()) }
  scope :ended, -> { where('end_time < ?', Time.now()) }
  scope :not_started_yet, -> { where('start_time > ?', Time.now()) }
  scope :active, -> { where( status: 1)}

  # include url helper to generate invitation link
  include Rails.application.routes.url_helpers

  after_initialize :init


  attr_accessor :time_status

  has_one :organization

  has_many :competition_members
  has_many :active_competition_members
  has_many :members, through: :competition_members, class_name: "User"
  # has_and_belongs_to_many :members, :class_name => "User", :join_table => "competition_members", :association_foreign_key => "member_id"
  # has_and_belongs_to_many :currencies, :join_table => "competition_currencies" # add af atrribute
  has_many :currencies, through: :competition_currencies
  has_many :competition_currencies
  has_many :teams, autosave: true
  has_many :team_members, through: :teams
  has_many :prizes, autosave: true
  has_many :activities, autosave: true
  has_many :activity_entries, through: :activities

  belongs_to :creator, class_name: 'User', foreign_key: 'created_by'
  belongs_to :organization

  validates :name, presence: true
  validates :created_by, presence: true, numericality: { only_integer: true }
  validates :start_time, presence: true, datetime: true
  validates :end_time, presence: true, datetime: true
  validates :organization_id, presence: true, numericality: { only_integer: true }



  def self.create_comp(comp)
    c= Competition.new(comp[:competition])
    # add activities
    comp[:activities].each do |act|
      c.activities.new(act)
    end

    # add prizes
    if(comp[:prizes])
      comp[:prizes].each do |prize|
        c.prizes.new(prize)
      end
    end

    # add teams
    if (comp[:teams])

      comp[:teams].each do |team|
        team_members = nil # init team_members to nil, assume no team member
        if(team['team_members']) # why :team_members not working?

          team_members = team['team_members']
          team = team.except('team_members') # remvoe team members key from team
        end
        t = c.teams.new(team)

        #add team members to team, add member to competiiton
        if(team_members)
          team_members.each do |tm|
            t.team_members.new(tm)
            cm = c.competition_members.new(tm)
            cm.team = t # add team associate to competiiton member
          end
        end

      end
    end
    # add currencies
    if (comp[:currencies])
      comp[:currencies].each do |cc|
        if cc.has_key?(:default)
          c.competition_currencies.new({currency_id: cc[:id], default: cc[:default]})
        else
          c.competition_currencies.new({currency_id: cc[:id]})
        end
      end
    end

    # add creator as competition member
    c.competition_members.new comp[:cm] if comp[:cm]
    
    c.save!
    c
  end

  # return competition leader user
  def leader
    acms = self.active_competition_members
    acms[0].member unless acms.empty? || acms[0].score == 0
  end

  def check_on_fire(ae)
    key = "onfire_comp_#{self.id}"
    puts key
    # comp = act.competition
    onfire_setting = Rails.cache.read key
    # puts onfire_setting

    if onfire_setting && (onfire_setting[:user_id] == ae.user_id)
      if onfire_setting[:count] == Competition::ON_FIRE_COUNT-1
        # reach limit, set competition member to onfire
        ae.set_on_fire
      else
        # increase counter
        Rails.cache.write key, { user_id: ae.user_id, count: onfire_setting[:count]+1}  
      end
    else
      Rails.cache.write key, { user_id: ae.user_id, count: 1}
    end

    puts Rails.cache.read key
  end

  # get default currency for cash comp
  def default_currency
    self.competition_currencies.find_by_default(true).currency if not self.competition_currencies.blank?
  end

  # duration calculation
  def duration(percent = 1)
    (self.end_time-self.start_time)*percent
  end

  # get competition status time wise
  def time_status
    if Time.now < self.start_time
      :not_started_yet
    elsif Time.now > self.end_time
      :ended
    else
      :ongoing
    end
  end

  def is_ended?
    self.time_status == :ended
  end

  def on_going?
    self.time_status == :ongoing
  end

  def not_started_yet?
    self.time_status == :not_started_yet
  end

  # check if user participant in this competiiton
  def has_member?(user)
    self.active_members.include? user
  end

  def admins
    self.creator.organization.admins
  end

  # notice: return value is array, not association
  def active_competition_members
    @active_competition_members ||= self.competition_members.select do |cm|
      cm.status == 1
    end
  end

  def active_members
    @active_members ||= self.members.where(competition_members: {status: 1}, status: 1)
  end

  def leaderboard_view_by?(user)
    self.admins.include?(user) || self.active_members.include?(user) || user.super_user?
  end

  # def organization_id
  #   @organization_id ||= self.creator.organization.id
  # end
  
  # generate invitation link path
  def invitation_path
    url_for controller: :competitions, action: :invite, competition_id: self.random_id, organization_id: self.creator.organization.random_id, only_path: true
  end

  # check if competition is team based or not
  def team_based?
    @team_based ||= !self.teams.empty?
  end

  # current user join competiiton
  def join(user,team=nil)
    if(team) # team based competition
      cm = self.competition_members.create!({member_id: user.id, team_id: team.id})
      team.team_members.create!(member_id: user.id)
    else # none team based competition
      cm = self.competition_members.create!({member_id: user.id})
    end
    cm
  end

  def about_start_time
    self.start_time - 1.day
  end

  def about_end_time
    diff = self.end_time - self.start_time
    self.end_time - diff*PERCENTATE
  end

  # add time_status attribute
  def attributes
    super.merge({ 'time_status' => time_status })
  end


  # generate a pusher channel for each competition
  def pusher_channel
    "comp-#{self.random_id}"
  end

  # duplicate a existing competition
  def duplicate_with_associations(creator= nil)

    Competition.transaction do
      @new_comp = self.deep_clone include: [:prizes, :competition_members,:activities,:teams, :competition_currencies], except: [ competition_members: [:score,:on_fire]] do |original, copy|
        # binding.pry
        # track old team id for copied team assignment
        copy.copy_from_id = original.id if original.class == Team and copy.respond_to? :copy_from_id  
      end
      # update attributes
      @new_comp.name = "Copy of #{self.name}"
      @new_comp.creator = creator if creator
      @new_comp.start_time = Time.now + 1.days
      @new_comp.end_time = @new_comp.start_time + self.duration
      @new_comp.random_id = SecureRandom.uuid

      @new_comp.save!

      # update team_id if competition is team based
      if @new_comp.team_based?
        # build map for old and new team
        @team_map = {}

        @new_comp.teams.each do |t|
          @team_map[t.copy_from_id] = t.id
        end

        @new_comp.competition_members.each do |cm|
          cm.team_id = @team_map[cm.team_id]
          cm.save!
        end
      end

      @new_comp
    end
  end

  # statistics
  def acts_count
    @act_count ||= lambda {
      act_count = []
      self.activities.each do |act|
        count = act.activity_entries.size
        act_count << {key: act.name, y: count} if count>0
      end
      act_count
    }.call
  end

  def acts_score_count
    @score_count ||= lambda {
      score_count = []
      self.activities.each do |act|
        score = ActivityEntry.where(activity_id: act.id).sum(:score).round(2)
        score_count << {key: act.name, y: score} if score>0
      end
      score_count
    }.call
  end

  # get last activity entry id
  def last_ae_id
    self.activity_entries.blank? ? nil : self.activity_entries.first.id
  end

  def populate_acts_line_data(type=1)
    sql = "select count(*), sum(aes.score), date_trunc('day', aes.created_at) as create_date, activity_id, competition_id from activity_entries as aes inner join activities as acs on aes.activity_id = acs.id where competition_id = #{self.id} group by date_trunc('day',aes.created_at), activity_id, competition_id order by create_date"
    records_array = ActiveRecord::Base.connection.execute(sql)

    # initiate data matrix
    matrix = {}
    self.activities.each do |act|
      matrix[act.id] = {}

      self.stats_date_range.each do |d|
        matrix[act.id][d.to_time.to_i] = 0 # initiate all field with 0
      end

    end
    # populate matrix
    records_array.each do |r|
      count = r['count'].to_i
      sum = r['sum'].to_f
      date = r['create_date'].to_time.to_i
      activity_id = r['activity_id'].to_i

      if type == 1
        matrix[activity_id][date] = count
      else
        matrix[activity_id][date] = sum
      end if (matrix.has_key? activity_id) and (matrix[activity_id].has_key? date) # only set if key exists

    end

    results = [] # array to store final formated data
    # convert from hash to array
    self.activities.each do |act|
      results << { key: act.name, values: matrix[act.id].to_a}
    end

    puts results
    results


  end

  # get date range to show in statistics
  def stats_date_range(d = 7)
    drange = self.start_time.to_date .. self.end_time.to_date
    if self.time_status === :ongoing
      drange= (Time.now - d.days).to_date .. Time.now.to_date
    end
    
    drange.to_a

  end

  ####################################
  # callback section
  ####################################
  # after_update do |competition|
  #   puts 'updated'
  # end

  after_save do |competition|
    begin
      if competition.start_time_changed? and competition.about_start_time > Time.now # reschedule about start worker
        CompetitionAboutStartWorker.perform_at(competition.about_start_time, competition.id, competition.about_start_time)
        puts 'about start scheduled'
      end

      if competition.end_time_changed?
        # reschedule about end worker
        CompetitionAboutEndWorker.perform_at(competition.about_end_time, competition.id, competition.about_end_time)
        puts 'about end scheduled'
        # reschedule end worker
        CompetitionEndedWorker.perform_at(competition.end_time, competition.id, competition.end_time)
        puts 'ended scheduled'
      end
    rescue => e
      # Do nothing now
    end

  end

  ####################################
  # private section
  ####################################
  private
    # initialize new record
    def init
      self.status ||= 1
      self.type_id ||= 1
      self.random_id ||= SecureRandom.uuid
      self.specific_time ||= false
    end

    # help function, duration in days
    def duration_in_days
      (self.duration/ 1.day).ceil
    end

end