added first stab at a kiosk mode
[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. 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 kiosk?
94     kiosk
95   end
96   
97   def shortdesc
98     shortdesc = description.split(/\n/)[0]
99   end
100
101   def longdesc
102     longdesc = description.split(/\n/)[1..-1].join("")
103     longdesc.length > 0 ? longdesc : nil 
104   end
105   
106   #Calculate results if not in memcache
107   def results
108     # Assignment is intentional
109     if Cache and c = Cache.get("election_results:#{id}:#{self.votes.length}")
110       @plurality_result = c['plurality']
111       @approval_result = c['approval']
112       @condorcet_result = c['condorcet']
113       @ssd_result = c['ssd']
114       @borda_result = c['borda']
115       return c
116     elsif Cache
117       # memcache is available, but missed.
118       results = self.results!
119       Cache.set("election_results:#{id}:#{self.votes.length}", results)
120       return results
121     else
122       return self.results!
123     end
124   end
125
126   #Always Calculate Election Results
127   def results!
128     # initalize the tallies to empty arrays
129     preference_tally = Array.new
130     plurality_tally = Array.new
131     approval_tally = Array.new
132
133     self.voters.each do |voter|
134       # skip if the voter has not voted or has an unconfirmed vote
135       next unless voter.voted?
136
137       plurality_tally << voter.vote.rankings.sort[0].candidate.id
138       approval_tally << voter.vote.rankings.sort[0..1].collect \
139         { |ranking| ranking.candidate.id }
140       preference_tally << voter.vote.rankings.sort.collect \
141         { |ranking| ranking.candidate.id }
142     end
143     
144     @plurality_result = PluralityVote.new(plurality_tally).result
145     @approval_result = ApprovalVote.new(approval_tally).result
146     @condorcet_result = PureCondorcetVote.new(preference_tally).result
147     @ssd_result = CloneproofSSDVote.new(preference_tally).result
148     @borda_result = BordaVote.new(preference_tally).result
149     
150     { 'plurality' => @plurality_result,
151       'approval' => @approval_result,
152       'condorcet' => @condorcet_result,
153       'ssd' => @ssd_result,
154       'borda' => @borda_result }
155     end
156   
157   def names_by_id
158     names = Hash.new
159     
160     competitors = self.candidates.sort.collect {|candidate| candidate.id}
161     competitors.each do |candidate|
162       names[candidate] = Candidate.find(candidate).name
163     end
164     
165     names
166   end
167 end
168
169

Benjamin Mako Hill || Want to submit a patch?