Skip to content

Instantly share code, notes, and snippets.

@fuyi
Last active August 29, 2015 14:13
Show Gist options
  • Save fuyi/b6d2987698e857d65f98 to your computer and use it in GitHub Desktop.
Save fuyi/b6d2987698e857d65f98 to your computer and use it in GitHub Desktop.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment