added support for drag and drop preferential quick voting
author<mako@atdot.cc> <>
Sat, 14 Oct 2006 05:56:51 +0000 (01:56 -0400)
committer<mako@atdot.cc> <>
Sat, 14 Oct 2006 05:56:51 +0000 (01:56 -0400)
app/controllers/quickvote_controller.rb
app/models/ranking.rb
app/models/vote.rb
app/models/voter.rb
app/views/quickvote/index.rhtml
config/environment.rb
config/routes.rb
public/stylesheets/hc.css

index 97f4ab05fb726bd0a03cdce10ee88c025cbbfd98..c897f369f33018bd9c3b5ffec552e20bdc0585ce 100644 (file)
@@ -1,23 +1,13 @@
 class QuickvoteController < ApplicationController
   layout 'hc'
   model :quick_voter
+  model :quick_vote
   model :vote
   model :election
 
-  def index
-    @election = QuickVote.find_all(["name = ?", params[:votename]])[0]
-
-    if @election
-      @voter = QuickVoter.find_all(["session_id = ? and election_id = ?",
-                                  session.session_id, @election.id])[0]
-      unless @voter 
-        @voter = QuickVoter.new
-        @voter.election = QuickVote.find_all( [ "name = ?", params[:votename] ] )[0]
-      end
-    else
-      redirect_to :controller => 'site'
-    end
-  end
+  #############################################################
+  # the following methods pertain to creating quickvotes
+  #############################################################
 
   def create
     if params[:quickvote] 
@@ -34,6 +24,7 @@ class QuickvoteController < ApplicationController
       else
         flash.keep(:candlist)
       end 
+
     else
       # if we don't have a quickvote param, it means that the person
       # here has not been hitting this page and we can clear any
@@ -52,45 +43,117 @@ class QuickvoteController < ApplicationController
     flash.keep(:candlist)
     render_partial 'candidate_list'
   end
+  #############################################################
+  # the following methods pertain to *voting* in the quickvotes
+  #############################################################
 
-  def change
-    voter = QuickVoter.find_all(["session_id = ?", session.session_id])[0]
-    voter.destroy
-    redirect_to quickvote_url( :votename => params[:votename] )
+  def index
+    @election = QuickVote.find_all(["name = ?", params[:votename]])[0]
+    
+    # if the person has specified an election, we show them the voting
+    # page. otherwise, we redirect back to main the page
+    if @election
+
+      # look to see that the voter has been created and has voted in
+      # this election, and has confirmed their vote
+      @voter = QuickVoter.find_all(["session_id = ? and election_id = ?",
+                                  session.session_id, @election.id])[0]
+
+      # if the voter has not voted we destroy them
+      if @voter and not @voter.voted?
+        @voter.destroy
+       @voter = nil
+      end
+
+      # if the voter does not exist or as has been destroyed, lets
+      # create a new one
+      unless @voter
+        # create a new voter and populate it
+        @voter = QuickVoter.new
+        @voter.election = QuickVote.find_all( [ "name = ?", params[:votename] ] )[0]
+        @voter.session_id = session.session_id
+
+       # create new vote and make it the defaulted sorted list
+        @voter.vote = Vote.new
+       @voter.save
+       @voter.vote.set_defaults!
+       @voter.reload
+      end
+    else
+      redirect_to :controller => 'site'
+    end
   end
 
   def confirm
-    election = QuickVote.find_all(["name = ?", params[:votename]])[0]
+    # we need the election to verify that we have the right voter
+    election = QuickVote.find_all( [ "name = ?", params[:votename] ] )[0]
+
+    # find out who the voter is for this election
+    @voter = QuickVoter.find_all(["session_id = ? and election_id = ?", 
+                                  session.session_id, election.id])[0]
 
-    if QuickVoter.find_all(["session_id = ? and election_id = ?", 
-                            session.session_id, election.id])[0]
+    if not @voter
+      # we have not seen this  voter before. something is wrong, try
+      # again
+      redirect_to quickvote_url( :votename => params[:votename] ) 
+      
+    elsif @voter.voted?
+      # this person has already voted, we try again
       flash[:notice] = "You have already voted!"
       redirect_to quickvote_url( :votename => params[:votename] )
+      
     else
-      @voter = QuickVoter.new()
-      @voter.election = election
-      @voter.session_id = session.session_id
+      # record the ip address for posterity
       @voter.ipaddress = request.env["REMOTE_ADDR"]
       @voter.save
-      @voter.reload
-        
-      @voter.vote = Vote.new
-      @voter.vote.votestring = params[:vote][:votestring]
+      
+      # toggle the confirmation bit
       @voter.vote.confirm!
+      @voter.reload
       render :action => 'thanks'
     end
   end
-  
+  def change
+    voter = QuickVoter.find_all(["session_id = ?", session.session_id])[0]
+    voter.destroy
+    redirect_to quickvote_url( :votename => params[:votename] )
+  end
+
+  def sort_candidates
+    @vote = Vote.find(params[:id])
+
+    @vote.rankings.each do |ranking|
+      ranking.rank = params['rankings-list'].index(ranking.candidate.id.to_s) + 1
+      ranking.save
+    end
+    render :nothing => true
+  end
+               
+
+  ###############################################################
+  # the following method pertains to displaying the results of a
+  # quickvote
+  ###############################################################
+
   def results
     @election = QuickVote.find_all( ["name = ?", params[:votename]] )[0]
 
-    preference_tally = []
-    plurality_tally = []
-    approval_tally = []
+    # initalize the tallies to empty arrays
+    preference_tally = Array.new
+    plurality_tally = Array.new
+    approval_tally = Array.new
+
     @election.voters.each do |voter|
+      # skip if the voter has not voted or has an unconfirmed vote
+      next unless voter.voted?
+      
       plurality_tally << voter.vote.rankings.sort[0].candidate.id
-      approval_tally << voter.vote.rankings.sort[0..1].collect {|ranking| ranking.candidate.id}
-      preference_tally << voter.vote.rankings.sort.collect {|ranking| ranking.candidate.id}
+      approval_tally << voter.vote.rankings.sort[0..1].collect \
+        { |ranking| ranking.candidate.id }
+      preference_tally << voter.vote.rankings.sort.collect \
+        { |ranking| ranking.candidate.id }
     end
  
     @plurality_result = PluralityVote.new(plurality_tally).result
index ba021a92c5489369e75b92094ffe549ccf5c1730..5ef0575adc1a9525a41dc6468c3406aad1132974 100644 (file)
@@ -1,6 +1,7 @@
 class Ranking < ActiveRecord::Base
   belongs_to :candidate
   belongs_to :vote
+  acts_as_list :scope => :vote, :column => :rank
 
   def <=>(other)
     self.rank <=> other.rank
index 014899d905ac9e07a185cd99f5abcae9db1d702f..33927cd16db995528e15da3aa6ab943353d5bc09 100644 (file)
@@ -13,7 +13,7 @@ class Vote < ActiveRecord::Base
   end
 
   def each
-    votes.each {|vote| yield vote}
+    self.votes.each {|vote| yield vote}
   end
 
   def votes
@@ -34,10 +34,10 @@ class Vote < ActiveRecord::Base
 
   def save_rankings
     destroy_rankings
-    self.votes.each_with_index do |candidate, index| 
+    self.votes.each_with_index do |candidate_id, index| 
       ranking = Ranking.new
       ranking.rank = index
-      ranking.candidate =  Candidate.find(candidate)
+      ranking.candidate =  Candidate.find(candidate_id)
       self.rankings << ranking
     end
   end
@@ -80,4 +80,11 @@ class Vote < ActiveRecord::Base
     self.votes.join("")
   end
 
+  # the following subroutine is used for quickvotes. it creates a vote
+  # with the candidates listed in order of preference based on
+  # alphabetical order. it is meant to be manipulated and then confirmed
+  def set_defaults!
+    self.votes =  voter.election.candidates.sort.collect {|c| c.id }
+    self.save
+  end
 end
index 62466755db4790175cfbc3e1712872ab8ba4698a..f229909814c12fbd51beeb120061661ef87a67ec 100644 (file)
@@ -6,6 +6,10 @@ class Voter < ActiveRecord::Base
     vote.destroy if vote
     super
   end
+
+  def voted?
+    vote.confirmed == 1
+  end
 end
 
 
index 84211ff77eb4c25e55bb324630769d57145f7b96..bb0b5a4705623d91e2cc7a5bb29ab4d771c07831 100644 (file)
@@ -13,7 +13,7 @@
 <h2>Vote</h2>
 <% end %>
 
-<% if @voter.session_id %>
+<% if @voter.voted? %>
   <p>You have already voted. You can:</p>
   
   <ul>
     <li><%= link_to "View election results.", quickaction_url( :votename => @voter.election.name, :action => 'results' ) %></li>
   </ul>
 <% else %>
-  <%= render_partial 'voter/vote' %>
+
+<p>Drag and drop the items on the following list until they are in order
+<em>from most preferred at the top to least preferred at the
+bottom</em>. When you are done, press confirm to record your vote.</p>
+
+<div id="sortable_list">
+<ol id="rankings-list">
+  <% for ranking in @voter.vote.rankings %>
+    <li class="moveable" id="ranking_<%= ranking.candidate.id %>">
+      <%= ranking.candidate.name.capitalize %></li>
+  <% end %>
+</ol>
+</div>
+
+<div class="clearbox"></div>
+
+<%= button_to "Confirm Vote", quickaction_url( :action => 'confirm', :votename => @voter.election.name)  %>
+
+<%= sortable_element 'rankings-list',
+    :url => { :action => "sort_candidates" , :id => @voter.vote.id },
+    :complete => visual_effect(:highlight, 'rankings-list') %> 
+
 <% end %>
index 6edda72a170c04f10d6fbd262bb4386bd3921bf3..f432b2dcb7e089cfa97d94bcce9907adfd019ab2 100644 (file)
@@ -83,8 +83,10 @@ module LoginEngine
   config :changeable_fields, []
   config :use_email_notification, true
   config :confirm_account, false
+end
 Engines.start :login
 
 # action mailer configuration
 ActionMailer::Base.delivery_method = :sendmail
 ActionMailer::Base.default_charset = "utf-8"
+
index c914447dccc5c090347cfc23abc8fd16d793c7b0..e400b5d8799df320cf22e328a1df1d08fa85527d 100644 (file)
@@ -15,8 +15,7 @@ ActionController::Routing::Routes.draw do |map|
  
   map.connect 'quickvote/:action/:id',
               :controller => 'quickvote',
-             :requirements => { :action => /(create|add_candidate)/ }
-             
+             :requirements => { :action => /(create|add_candidate|sort_candidates)/ }
 
   map.quickaction 'quickvote/:votename/:action',
                   :controller => 'quickvote',
index 62c3a364b70795c6421b67806426a0a047860a80..4056f8ef0a2c6b526714aaa87d52c8c4c1faa538 100644 (file)
@@ -147,3 +147,16 @@ a:active { color: #FFFFFF; text-decoration: none; background: #0259C4; }
    margin: 30px;
 }
 
+li.moveable {
+  background-color: #EEDCE1;
+  border:1px solid #BD7589;
+  cursor: move;
+  padding: 4px;
+  margin: 4px;
+}
+
+#sortable_list {
+  font-size: 24pt;
+  display: float;
+  float: left;
+}

Benjamin Mako Hill || Want to submit a patch?