+# Selectricity: Voting Machinery for the Masses
+# Copyright (C) 2007, 2008 Benjamin Mako Hill <mako@atdot.cc>
+# Copyright (C) 2007 Massachusetts Institute of Technology
+#
+# This program is free software. Please see the COPYING file for
+# details.
+
class Election < ActiveRecord::Base
has_many :candidates
+ has_many :voters
+ has_many :votes
+ belongs_to :user
validates_presence_of :name, :description
+ # enforce constraints associated with dependencies (i.e., a kiosk
+ # election can't also be unauthenticated)
+ before_save :enforce_constraints
+
+ #validate that method is one of the listed election types
+ attr_reader :plurality_result
+ attr_reader :approval_result
+ attr_reader :condorcet_result
+ attr_reader :ssd_result
+ attr_reader :borda_result
+
+ require 'date'
+
+ def initialize(params={})
+ super
+ self.enddate = read_attribute( :enddate ) || \
+ Time.now + 30.days - 1.second
+ end
+
+ def other_methods
+ if election_method
+ @other_methods = ELECTION_TYPES.keys.reject {|i| i == election_method}
+ else
+ @other_methods = nil
+ end
+ @other_methods
+ end
+
+ def startdate
+ read_attribute( :startdate ) || Time.now
+ end
+
+ def votes
+ votes = Array.new
+ self.voters.each do |voter|
+ votes << voter.vote
+ end
+ return votes
+ end
+
def destroy
self.candidates.each do |candidate|
candidate.destroy
end
- super destroy
+ super
+ end
+
+ def start_blockers
+ reasons = []
+ if self.candidates.length <= 1
+ reasons << "You must have at least two candidates."
+ end
+
+ if self.voters.length <= 1 and self.authenticated?
+ reasons << "You must have at least two voters."
+ end
+
+ reasons
+ end
+
+ def activate!
+ self.active = 1
+ self.save
end
+ def quickvote?
+ self.class == QuickVote
+ end
+
+ def active?
+ active == 1
+ end
+
+ def done?
+ active == 2
+ end
+
+ def authenticated?
+ authenticated
+ end
+
+ def kiosk?
+ kiosk
+ end
+
+ def shortdesc
+ shortdesc = description.split(/\n/)[0]
+ end
+
+ def longdesc
+ longdesc = description.split(/\n/)[1..-1].join("")
+ longdesc.length > 0 ? longdesc : nil
+ end
+
+ #Calculate results if not in memcache
+ def results
+ # Assignment is intentional
+ if Cache and c = Cache.get("election_results:#{id}:#{self.votes.length}")
+ @plurality_result = c['plurality']
+ @approval_result = c['approval']
+ @condorcet_result = c['condorcet']
+ @ssd_result = c['ssd']
+ @borda_result = c['borda']
+ return c
+ elsif Cache
+ # memcache is available, but missed.
+ results = self.results!
+ Cache.set("election_results:#{id}:#{self.votes.length}", results)
+ return results
+ else
+ return self.results!
+ end
+ end
+
+ #Always Calculate Election Results
+ def results!
+ # initalize the tallies to empty arrays
+ preference_tally = Array.new
+ plurality_tally = Array.new
+ approval_tally = Array.new
+
+ self.voters.each do |voter|
+ # skip if the voter has not voted or has an unconfirmed vote
+ next unless voter.voted?
+
+ plurality_tally << voter.vote.rankings.sort[0].candidate_id
+ approval_tally << voter.vote.rankings.sort[0..1].collect \
+ { |ranking| ranking.candidate_id }
+ preference_tally << voter.vote.rankings.sort.collect \
+ { |ranking| ranking.candidate_id }
+ end
+
+ @plurality_result = PluralityVote.new(plurality_tally).result
+ @approval_result = ApprovalVote.new(approval_tally).result
+ @condorcet_result = PureCondorcetVote.new(preference_tally).result
+ @ssd_result = CloneproofSSDVote.new(preference_tally).result
+ @borda_result = BordaVote.new(preference_tally).result
+
+ { 'plurality' => @plurality_result,
+ 'approval' => @approval_result,
+ 'condorcet' => @condorcet_result,
+ 'ssd' => @ssd_result,
+ 'borda' => @borda_result }
+ end
+
+ def names_by_id
+ names = Hash.new
+
+ competitors = self.candidates.sort.collect {|candidate| candidate.id}
+ competitors.each do |candidate|
+ names[candidate] = Candidate.find(candidate).name
+ end
+
+ names
+ end
+
+ def candidate_hash
+ hash = {}
+ self.candidates.each {|c| hash[c.id] = c}
+ return hash
+ end
+
+
+ # TODO now that this code is in here, we should go ahead and remove
+ # date checking from other places in the code
+ def after_find
+ if self.active < 2 and self.enddate < Time.now
+ self.active = 2
+ self.save
+ end
+ end
+
+ private
+ def enforce_constraints
+ # kiosks can't be authenticated
+ self.authenticated = false if kiosk?
+ return true
+ end
+
end
+
+