Variety of improvements and additions:
author<mako@atdot.cc> <>
Fri, 14 Jul 2006 22:50:49 +0000 (18:50 -0400)
committer<mako@atdot.cc> <>
Fri, 14 Jul 2006 22:50:49 +0000 (18:50 -0400)
 * Much improved list screen for elections.
 * Initial voter-based interface based around tokens.
 * New uniq_token regenerating library.

Left things a little bit unstable including:

 * uniq_token should be refactored to provide only a class method.
 * voter list review page is unwritten
 * rankings are committed to the database but some sort somewhere seems
   to be botching things.

18 files changed:
app/controllers/elections_controller.rb
app/controllers/voter_controller.rb [new file with mode: 0644]
app/helpers/voter_helper.rb [new file with mode: 0644]
app/models/candidate.rb
app/models/election.rb
app/models/ranking.rb [new file with mode: 0644]
app/models/raw_voter_list.rb
app/models/vote.rb [new file with mode: 0644]
app/models/voter.rb
app/models/votes.rb [deleted file]
app/views/elections/list.rhtml
app/views/voter/index.rhtml [new file with mode: 0644]
lib/uniq_token.rb [new file with mode: 0644]
test/fixtures/rankings.yml [moved from test/fixtures/votes.yml with 100% similarity]
test/fixtures/vote.yml [new file with mode: 0644]
test/functional/voter_controller_test.rb [new file with mode: 0644]
test/unit/ranking_test.rb [new file with mode: 0644]
test/unit/vote_test.rb [moved from test/unit/votes_test.rb with 100% similarity]

index 696028e1287baabe6f3431ea12c337bcee0e4238..b3406a84ab64941ce8a4c05e415937aec09f4e92 100644 (file)
@@ -1,3 +1,5 @@
+require 'uniq_token'
+
 class ElectionsController < ApplicationController
   model :raw_voter_list, :voter, :vote, :candidate
 
@@ -104,10 +106,17 @@ class ElectionsController < ApplicationController
 
     def process_incoming_voters(raw_voter_list)
       incoming_voters = RawVoterList.new( raw_voter_list )
+      token_generator = UniqueTokenGenerator.new( 16 )
 
       unless incoming_voters.entries.empty?
         incoming_voters.each do |new_voter|
 
+         until new_voter.password and \
+               Voter.find_all( [ "password = ?", new_voter.password ]).empty?
+           new_voter.password = token_generator.token
+         end
+        
+         breakpoint
           if incoming_voters.email == 0
             new_voter.contacted = 1
          elsif incoming_voters.email == 1
@@ -127,4 +136,8 @@ class ElectionsController < ApplicationController
       @raw_voter_list = RawVoterList.new
       @raw_voter_list.email = incoming_voters.email
     end
+
+    def email_voter
+    end
+
 end
diff --git a/app/controllers/voter_controller.rb b/app/controllers/voter_controller.rb
new file mode 100644 (file)
index 0000000..2ceb42b
--- /dev/null
@@ -0,0 +1,25 @@
+class VoterController < ApplicationController
+  model :voter
+  model :vote
+  model :election
+
+  def index
+    password = params[:id]
+    @voter = Voter.find_all( [ "password = ?", password ] )[0]
+  end
+  
+  def review
+    password = params[:id]
+    @voter = Voter.find_all( [ "password = ?", password ] )[0]
+    
+    # destroy the old vote if that's what we need to do
+    @voter.vote.destroy if @voter.vote 
+    @voter.reload
+
+    @voter.vote = Vote.new
+    @voter.vote.votestring = params[:vote][:votestring] 
+    @voter.vote.save
+    render_text "success"
+  end
+
+end
diff --git a/app/helpers/voter_helper.rb b/app/helpers/voter_helper.rb
new file mode 100644 (file)
index 0000000..7477e94
--- /dev/null
@@ -0,0 +1,2 @@
+module VoterHelper
+end
index 44a4d63ff4660f6558d8146bf1c6801d3627e3ba..a0e492e8c1f98a6848efc5215db07de5b62f4c7a 100644 (file)
@@ -1,3 +1,8 @@
 class Candidate < ActiveRecord::Base
   belongs_to :election
+
+  def <=>(other)
+   self.name <=> other.name 
+  end
+
 end
index 29bf1b03e4897ad36f5345b3ec6521e514a8abdf..61286e1c1829c00e5880dacffda74370de7033bb 100644 (file)
@@ -18,7 +18,7 @@ class Election < ActiveRecord::Base
     self.candidates.each do |candidate|
       candidate.destroy
     end
-    super destroy
+    super
   end
   
 end
diff --git a/app/models/ranking.rb b/app/models/ranking.rb
new file mode 100644 (file)
index 0000000..f24f0f9
--- /dev/null
@@ -0,0 +1,4 @@
+class Ranking < ActiveRecord::Base
+  belongs_to :candidate
+  belongs_to :vote
+end
index 2f7cb28b1ae62f2ca6ec46c56d2fb8eecf07bbe5..71b200f603f313db269a5cae7714ebe84af8b957 100644 (file)
@@ -6,7 +6,7 @@ class RawVoterList
   include Enumerable
 
   def initialize(params={})
-    @email = params[:email] || 1
+    @email = params[:email].to_i || 1
     @input_addresses = params[:input_addresses] || String.new
   end
 
diff --git a/app/models/vote.rb b/app/models/vote.rb
new file mode 100644 (file)
index 0000000..2aa1c4b
--- /dev/null
@@ -0,0 +1,38 @@
+class Vote < ActiveRecord::Base
+  belongs_to :voter
+  has_many :rankings
+  
+  def initialize
+    super
+    @votes = []
+  end
+  
+  def votestring=(string="")
+    rel_votes = string.split("").collect { |vote| vote.to_i }
+    
+    # covert relative orders to absolute candidate ids
+    candidate_ids = voter.election.candidates.sort
+    candidate_ids.collect! { |candidate| candidate.id.to_i }
+   
+    rel_votes.collect! { |vote| candidate_ids[ vote - 1 ] }
+    @votes = rel_votes
+  end
+
+  def save 
+    rankings.each { destroy } unless rankings.empty?
+    @votes.each_with_index do |candidate, index| 
+      ranking = Ranking.new
+      ranking.rank = index + 1
+      ranking.candidate =  Candidate.find(candidate)
+      self.rankings << ranking
+    end
+      
+    super
+  end
+
+  def destroy
+    rankings.each { destroy }
+    super
+  end
+
+end
index a63ec67a68ccbf7c8e4d752bed82a0ef081fd9fd..13b43f525ac7882ac85d9c36e86483213d4573e1 100644 (file)
@@ -1,3 +1,4 @@
 class Voter < ActiveRecord::Base
   belongs_to :election
+  has_one :vote
 end
diff --git a/app/models/votes.rb b/app/models/votes.rb
deleted file mode 100644 (file)
index 782f514..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-class Votes < ActiveRecord::Base
-end
index 40e24b5fbf039d7036d1e4ba79ba23d82d74614d..83be6c6c28671bf728c27ce8e36b628cfa2a1734 100644 (file)
@@ -1,20 +1,28 @@
+<% %>
 <h1>Listing elections</h1>
 
-<table>
-  <tr>
-  <% for column in Election.content_columns %>
-    <th><%= column.human_name %></th>
-  <% end %>
-  </tr>
-  
+<table cellpadding="10px">
+
 <% for election in @elections %>
   <tr>
-  <% for column in Election.content_columns %>
-    <td><%=h election.send(column.name) %></td>
-  <% end %>
-    <td><%= link_to 'Show', :action => 'show', :id => election %></td>
-    <td><%= link_to 'Edit', :action => 'edit', :id => election %></td>
-    <td><%= link_to 'Destroy', { :action => 'destroy', :id => election }, :confirm => 'Are you sure?' %></td>
+    <td valign="top"><h2><%= link_to election.name, :action => 'show', :id => election %></h2>
+        <p><strong>Description:</strong></p>
+       <blockquote><%= election.description %></blockquote>
+       
+        <p><strong>Election Information:</strong></p>
+       <ul>
+          <li><%= election.voters.length %> registered voters</li>
+          <li><%= "<strong>Not</strong> " unless election.anonymous == 1 %>Anonymous</li>
+         <li>Starts <%= election.startdate %></li>
+         <li>Ends <%= election.enddate %></li>
+        </ul> 
+    </td>
+    <td valign="top">
+        <p><strong>Candidates:</strong></p>
+       <% @election = election %><%= render :partial => 'candidate_list', :id => election.id %>
+    </td>
+    <td valign="top">
+        <%= link_to 'Destroy', { :action => 'destroy', :id => election }, :confirm => 'Are you sure?' %></td>
   </tr>
 <% end %>
 </table>
diff --git a/app/views/voter/index.rhtml b/app/views/voter/index.rhtml
new file mode 100644 (file)
index 0000000..00b5136
--- /dev/null
@@ -0,0 +1,35 @@
+<% %>
+
+<h1>Vote Below the Line</h1>
+
+<p><strong>Election:</strong> <%= @voter.election.name %></p>
+
+<p><strong>Voter:</strong> <%= @voter.email %></p>
+
+<p><strong>Candidates:</strong></p>
+
+<ol>
+<% for candidate in @voter.election.candidates %>
+  <li><%= candidate.name %></li>
+<% end %>
+</ol>
+
+<p>If this information is incorrect, please notify the vote
+administrator immediatedly!</p>
+
+<hr />
+
+<h2>Place Your Vote Here</h2>
+
+<p>Rank each candidate in order of more preferred to least
+preferred. (e.g., 123 or 321 or 213, etc.)</p>
+
+<%= form_tag :action => 'review', :id => @voter.password %>
+<%= text_field :vote, :votestring -%>
+<%= submit_tag "Submit!" %>
+<%= end_form_tag %>
+
+
+
+
+
diff --git a/lib/uniq_token.rb b/lib/uniq_token.rb
new file mode 100644 (file)
index 0000000..ec93546
--- /dev/null
@@ -0,0 +1,25 @@
+require 'digest/md5'
+
+class UniqueTokenGenerator
+  def initialize(length=10)
+    @length = length 
+  end
+
+  # this should probably be rewritten as a class method
+  def token
+    token = ""
+    while token.length < @length
+      seed = ""
+      16.times do
+        seed << ( i = Kernel.rand(62)
+                  i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 )) ).chr
+      end
+      token << Digest::MD5.hexdigest( seed )
+    end
+    token.slice( 0..@length )
+  end
+end
+
+# debug code
+# puts UniqueTokenGenerator.new(300).token
+
diff --git a/test/fixtures/vote.yml b/test/fixtures/vote.yml
new file mode 100644 (file)
index 0000000..8794d28
--- /dev/null
@@ -0,0 +1,5 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+first:
+  id: 1
+another:
+  id: 2
diff --git a/test/functional/voter_controller_test.rb b/test/functional/voter_controller_test.rb
new file mode 100644 (file)
index 0000000..992476b
--- /dev/null
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'voter_controller'
+
+# Re-raise errors caught by the controller.
+class VoterController; def rescue_action(e) raise e end; end
+
+class VoterControllerTest < Test::Unit::TestCase
+  def setup
+    @controller = VoterController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/test/unit/ranking_test.rb b/test/unit/ranking_test.rb
new file mode 100644 (file)
index 0000000..2e44da8
--- /dev/null
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class RankingTest < Test::Unit::TestCase
+  fixtures :rankings
+
+  # Replace this with your real tests.
+  def test_truth
+    assert_kind_of Ranking, rankings(:first)
+  end
+end

Benjamin Mako Hill || Want to submit a patch?