]> projects.mako.cc - selectricity/blob - app/models/vote.rb
fix bug that allowed votes with more rankings than candidates to be recorded
[selectricity] / app / models / vote.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 Vote < ActiveRecord::Base
20   # relationships to other classes
21   belongs_to :voter
22   has_many :rankings
23   has_one :token
24   
25   # callbacks
26   after_update :save_rankings
27   before_destroy :destroy_rankings
28   
29   def to_s
30     votes.join("")
31   end
32
33   def each
34     self.votes.each {|vote| yield vote}
35   end
36
37   def votes
38     unless @votes
39       if rankings.empty?
40         @votes = Array.new
41       else
42         @votes = self.rankings.sort.collect { |ranking| ranking.candidate.id }
43       end
44     end
45
46     @votes
47   end
48
49   def votes=(array)
50     @votes = array
51   end
52
53   def save_rankings
54     self.votes # i need to initalize this before destroying rankings
55                # or else the ranks themselves show up as nil
56
57     destroy_rankings
58     self.votes.each_with_index do |candidate_id, index| 
59       ranking = Ranking.new
60       ranking.rank = index
61       ranking.candidate =  Candidate.find(candidate_id)
62       self.rankings << ranking
63     end
64   end
65   
66   def destroy
67     self.destroy_rankings
68     super
69   end
70
71   def destroy_rankings 
72     rankings.each { |ranking| ranking.destroy }
73   end
74
75   def confirm!
76     if self.voter.election.candidates.length == self.rankings.length
77       self.confirmed = 1
78       self.time = Time.now
79       self.save
80     
81       unless self.voter.election.quickvote?
82         token.destroy and token.reload if token
83         self.token = Token.new
84         self.save
85       end
86       return false
87     else
88       return true
89     end
90   end
91
92   def confirm?
93     confirmed == 1
94   end
95   
96   def votestring
97     # create a mapping of candidates ids and the relative order of the
98     # candidates as they appear when sorted alphabetically
99     cand_relnums = {}
100     self.voter.election.candidates.sort.each_with_index do |c, i|
101       cand_relnums[c.id] = i + 1
102     end
103
104     # assemble the votestring
105     self.votes.collect {|v| (cand_relnums[v] + 64).chr}.join("")
106   end
107
108   # the following subroutine is used for quickvotes, but need for elections now
109   # too. It creates a vote with the candidates listed in order of preference 
110   # based on alphabetical order. Meant to be manipulated and then confirmed
111   def set_defaults!  
112     self.votes = self.voter.election.candidates.sort_by { rand }.collect {|c| c.id }
113     self.save
114   end
115          
116 end

Benjamin Mako Hill || Want to submit a patch?