app/models/contestant.rb
class Contestant < ActiveRecord::Base
has_many :runs, dependent: :destroy
has_many :heats, through: :runs
scope :ranked, -> do
with_average_time.
where('heats.sequence >= 0').
order('average_time')
end
scope :with_average_time, -> do
select('contestants.*, avg(runs.time) AS average_time').
group('contestants.id').
joins('LEFT JOIN runs ON runs.contestant_id = contestants.id LEFT JOIN heats ON heats.id = runs.heat_id')
end
validates :name, presence: true
def self.next_suitable(options = {})
exclude = Array(options[:exclude])
exclude = [Contestant.new] if exclude.empty?
exclude = exclude.map { |contestant| contestant.respond_to?(:id) ? contestant.id.to_i : contestant.to_i }
lane = options[:lane].to_i
max_runs_per_contestant = DerbyConfig.lane_count
# select
s = select('contestants.*')
# filter
s = s.where('contestants.id NOT IN (?)', exclude) if exclude.any?
s = s.where("contestants.retired IS NULL OR contestants.retired = ?", false)
s = s.group('contestants.id')
s = s.joins('LEFT JOIN runs ON runs.contestant_id = contestants.id')
s = s.having('count(DISTINCT heats.id) < ?', max_runs_per_contestant)
s = s.joins("LEFT JOIN runs AS runs_in_lane ON runs.contestant_id = contestants.id AND runs.lane = #{lane}")
s = s.having("count(runs_in_lane.id) = 0")
# order
s = s.joins('LEFT JOIN heats ON heats.id = runs.heat_id')
s = s.joins("LEFT JOIN runs AS runs_with_chosen ON runs_with_chosen.heat_id = heats.id AND runs_with_chosen.contestant_id IN (#{exclude.join(',')})")
s = s.select('count(runs_with_chosen.id) AS heat_count_with_chosen')
s = s.select('count(DISTINCT heats.id) AS heat_count')
s = s.select('avg(runs.time) AS average_run_time')
s = s.order('heat_count_with_chosen, heat_count, average_run_time DESC, contestants.created_at')
return s if options[:raw]
s.first
end
def self.generate(i = 26)
(?a..?z).to_a[0, i].map{|a|a.upcase + a*10}.each{|a|Contestant.create name: a}
end
def average_time
average_time = self[:average_time] || calculate_average_time
average_time.try :round, 4
end
def retire
transaction do
update_attributes! retired: true
runs.upcoming.readonly(false).each &:postpone
self
end
end
def reactivate
update_attributes! retired: false
self
end
private
def calculate_average_time
(self.class.ranked.where(id: id).first || {})[:average_time]
end
end