1 # Selectricity: Voting Machinery for the Masses
2 # Copyright (C) 2007, 2008 Benjamin Mako Hill <mako@atdot.cc>
3 # Copyright (C) 2007 Massachusetts Institute of Technology
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Affero General Public License for more details.
15 # You should have received a copy of the GNU Affero General Public
16 # License along with this program. If not, see
17 # <http://www.gnu.org/licenses/>.
19 class Election < ActiveRecord::Base
24 validates_presence_of :name, :description
26 #validate that method is one of the listed election types
28 attr_reader :plurality_result
29 attr_reader :approval_result
30 attr_reader :condorcet_result
31 attr_reader :ssd_result
32 attr_reader :borda_result
36 def initialize(params={})
38 self.enddate = read_attribute( :enddate ) || \
39 Time.now + 30.days - 1.second
44 @other_methods = ELECTION_TYPES.keys.reject {|i| i == election_method}
52 read_attribute( :startdate ) || Time.now
57 self.voters.each do |voter|
64 self.candidates.each do |candidate|
72 if self.candidates.length <= 1
73 reasons << "You must have at least two candidates."
76 if self.voters.length <= 1 and self.authenticated?
77 reasons << "You must have at least two voters."
89 self.class == QuickVote
105 shortdesc = description.split(/\n/)[0]
109 longdesc = description.split(/\n/)[1..-1].join("")
110 longdesc.length > 0 ? longdesc : nil
113 #Calculate results if not in memcache
115 # Assignment is intentional
116 if Cache and c = Cache.get("election_results:#{id}:#{self.votes.length}")
117 @plurality_result = c['plurality']
118 @approval_result = c['approval']
119 @condorcet_result = c['condorcet']
120 @ssd_result = c['ssd']
121 @borda_result = c['borda']
124 # memcache is available, but missed.
125 results = self.results!
126 Cache.set("election_results:#{id}:#{self.votes.length}", results)
133 #Always Calculate Election Results
135 # initalize the tallies to empty arrays
136 preference_tally = Array.new
137 plurality_tally = Array.new
138 approval_tally = Array.new
140 self.voters.each do |voter|
141 # skip if the voter has not voted or has an unconfirmed vote
142 next unless voter.voted?
144 plurality_tally << voter.vote.rankings.sort[0].candidate.id
145 approval_tally << voter.vote.rankings.sort[0..1].collect \
146 { |ranking| ranking.candidate.id }
147 preference_tally << voter.vote.rankings.sort.collect \
148 { |ranking| ranking.candidate.id }
151 @plurality_result = PluralityVote.new(plurality_tally).result
152 @approval_result = ApprovalVote.new(approval_tally).result
153 @condorcet_result = PureCondorcetVote.new(preference_tally).result
154 @ssd_result = CloneproofSSDVote.new(preference_tally).result
155 @borda_result = BordaVote.new(preference_tally).result
157 { 'plurality' => @plurality_result,
158 'approval' => @approval_result,
159 'condorcet' => @condorcet_result,
160 'ssd' => @ssd_result,
161 'borda' => @borda_result }
167 competitors = self.candidates.sort.collect {|candidate| candidate.id}
168 competitors.each do |candidate|
169 names[candidate] = Candidate.find(candidate).name