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

Benjamin Mako Hill || Want to submit a patch?