9ce89b9b6baae86009ebc4b7ebdaef096e4dd970
[selectricity-live] / app / models / election.rb
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
4 #
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.
9 #
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.
14 #
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/>.
18
19 class Election < ActiveRecord::Base
20   has_many :candidates
21   has_many :voters
22   has_many :votes
23   belongs_to :user
24   validates_presence_of :name, :description
25   
26   #validate that method is one of the listed election types
27   
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
33   
34   require 'date'
35   
36   def initialize(params={})
37     super
38     self.enddate = read_attribute( :enddate ) || \
39                    Time.now + 30.days - 1.second
40   end
41
42   def other_methods
43     if election_method
44       @other_methods = ELECTION_TYPES.keys.reject {|i| i == election_method}
45     else
46       @other_methods = nil
47     end
48     @other_methods
49   end
50
51   def startdate
52     read_attribute( :startdate ) || Time.now
53   end
54
55   def votes
56     votes = Array.new
57     self.voters.each do |voter|
58       votes << voter.vote
59     end
60     return votes
61   end
62
63   def destroy
64     self.candidates.each do |candidate|
65       candidate.destroy
66     end
67     super
68   end
69
70   def start_blockers
71     reasons = []
72     if self.candidates.length <= 1
73       reasons << "You must have at least two candidates."
74     end
75     
76     if self.voters.length <= 1 and self.authenticated?
77       reasons << "You must have at least two voters."
78     end
79
80     reasons
81   end
82
83   def activate!
84     self.active = 1
85     self.save!
86   end
87   
88   def quickvote?
89     self.class == QuickVote
90   end
91
92   def active?
93     active == 1
94   end 
95
96   def done?
97     active == 2
98   end
99
100   def authenticated?
101     authenticated
102   end
103   
104   def shortdesc
105     shortdesc = description.split(/\n/)[0]
106   end
107
108   def longdesc
109     longdesc = description.split(/\n/)[1..-1].join("")
110     longdesc.length > 0 ? longdesc : nil 
111   end
112   
113   #Calculate results if not in memcache
114   def results
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']
122       return c
123     elsif Cache
124       # memcache is available, but missed.
125       results = self.results!
126       Cache.set("election_results:#{id}:#{self.votes.length}", results)
127       return results
128     else
129       return self.results!
130     end
131   end
132
133   #Always Calculate Election Results
134   def results!
135     # initalize the tallies to empty arrays
136     preference_tally = Array.new
137     plurality_tally = Array.new
138     approval_tally = Array.new
139
140     self.voters.each do |voter|
141       # skip if the voter has not voted or has an unconfirmed vote
142       next unless voter.voted?
143
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 }
149     end
150     
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
156     
157     { 'plurality' => @plurality_result,
158       'approval' => @approval_result,
159       'condorcet' => @condorcet_result,
160       'ssd' => @ssd_result,
161       'borda' => @borda_result }
162     end
163   
164   def names_by_id
165     names = Hash.new
166     
167     competitors = self.candidates.sort.collect {|candidate| candidate.id}
168     competitors.each do |candidate|
169       names[candidate] = Candidate.find(candidate).name
170     end
171     
172     names
173   end
174 end
175
176

Benjamin Mako Hill || Want to submit a patch?