From 9abed97635edbac7fb1a687298fff5c5434cdff4 Mon Sep 17 00:00:00 2001 From: Date: Fri, 14 Jul 2006 18:50:49 -0400 Subject: [PATCH] Variety of improvements and additions: * 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. --- app/controllers/elections_controller.rb | 13 ++++++++ app/controllers/voter_controller.rb | 25 +++++++++++++++ app/helpers/voter_helper.rb | 2 ++ app/models/candidate.rb | 5 +++ app/models/election.rb | 2 +- app/models/ranking.rb | 4 +++ app/models/raw_voter_list.rb | 2 +- app/models/vote.rb | 38 +++++++++++++++++++++++ app/models/voter.rb | 1 + app/models/votes.rb | 2 -- app/views/elections/list.rhtml | 34 ++++++++++++-------- app/views/voter/index.rhtml | 35 +++++++++++++++++++++ lib/uniq_token.rb | 25 +++++++++++++++ test/fixtures/{votes.yml => rankings.yml} | 0 test/fixtures/vote.yml | 5 +++ test/functional/voter_controller_test.rb | 18 +++++++++++ test/unit/ranking_test.rb | 10 ++++++ test/unit/{votes_test.rb => vote_test.rb} | 0 18 files changed, 204 insertions(+), 17 deletions(-) create mode 100644 app/controllers/voter_controller.rb create mode 100644 app/helpers/voter_helper.rb create mode 100644 app/models/ranking.rb create mode 100644 app/models/vote.rb delete mode 100644 app/models/votes.rb create mode 100644 app/views/voter/index.rhtml create mode 100644 lib/uniq_token.rb rename test/fixtures/{votes.yml => rankings.yml} (100%) create mode 100644 test/fixtures/vote.yml create mode 100644 test/functional/voter_controller_test.rb create mode 100644 test/unit/ranking_test.rb rename test/unit/{votes_test.rb => vote_test.rb} (100%) diff --git a/app/controllers/elections_controller.rb b/app/controllers/elections_controller.rb index 696028e..b3406a8 100644 --- a/app/controllers/elections_controller.rb +++ b/app/controllers/elections_controller.rb @@ -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 index 0000000..2ceb42b --- /dev/null +++ b/app/controllers/voter_controller.rb @@ -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 index 0000000..7477e94 --- /dev/null +++ b/app/helpers/voter_helper.rb @@ -0,0 +1,2 @@ +module VoterHelper +end diff --git a/app/models/candidate.rb b/app/models/candidate.rb index 44a4d63..a0e492e 100644 --- a/app/models/candidate.rb +++ b/app/models/candidate.rb @@ -1,3 +1,8 @@ class Candidate < ActiveRecord::Base belongs_to :election + + def <=>(other) + self.name <=> other.name + end + end diff --git a/app/models/election.rb b/app/models/election.rb index 29bf1b0..61286e1 100644 --- a/app/models/election.rb +++ b/app/models/election.rb @@ -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 index 0000000..f24f0f9 --- /dev/null +++ b/app/models/ranking.rb @@ -0,0 +1,4 @@ +class Ranking < ActiveRecord::Base + belongs_to :candidate + belongs_to :vote +end diff --git a/app/models/raw_voter_list.rb b/app/models/raw_voter_list.rb index 2f7cb28..71b200f 100644 --- a/app/models/raw_voter_list.rb +++ b/app/models/raw_voter_list.rb @@ -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 index 0000000..2aa1c4b --- /dev/null +++ b/app/models/vote.rb @@ -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 diff --git a/app/models/voter.rb b/app/models/voter.rb index a63ec67..13b43f5 100644 --- a/app/models/voter.rb +++ b/app/models/voter.rb @@ -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 index 782f514..0000000 --- a/app/models/votes.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Votes < ActiveRecord::Base -end diff --git a/app/views/elections/list.rhtml b/app/views/elections/list.rhtml index 40e24b5..83be6c6 100644 --- a/app/views/elections/list.rhtml +++ b/app/views/elections/list.rhtml @@ -1,20 +1,28 @@ +<% %>

Listing elections

- - - <% for column in Election.content_columns %> - - <% end %> - - +
<%= column.human_name %>
+ <% for election in @elections %> - <% for column in Election.content_columns %> - - <% end %> - - - + + + <% end %>
<%=h election.send(column.name) %><%= link_to 'Show', :action => 'show', :id => election %><%= link_to 'Edit', :action => 'edit', :id => election %><%= link_to 'Destroy', { :action => 'destroy', :id => election }, :confirm => 'Are you sure?' %>

<%= link_to election.name, :action => 'show', :id => election %>

+

Description:

+
<%= election.description %>
+ +

Election Information:

+
    +
  • <%= election.voters.length %> registered voters
  • +
  • <%= "Not " unless election.anonymous == 1 %>Anonymous
  • +
  • Starts <%= election.startdate %>
  • +
  • Ends <%= election.enddate %>
  • +
+
+

Candidates:

+ <% @election = election %><%= render :partial => 'candidate_list', :id => election.id %> +
+ <%= link_to 'Destroy', { :action => 'destroy', :id => election }, :confirm => 'Are you sure?' %>
diff --git a/app/views/voter/index.rhtml b/app/views/voter/index.rhtml new file mode 100644 index 0000000..00b5136 --- /dev/null +++ b/app/views/voter/index.rhtml @@ -0,0 +1,35 @@ +<% %> + +

Vote Below the Line

+ +

Election: <%= @voter.election.name %>

+ +

Voter: <%= @voter.email %>

+ +

Candidates:

+ +
    +<% for candidate in @voter.election.candidates %> +
  1. <%= candidate.name %>
  2. +<% end %> +
+ +

If this information is incorrect, please notify the vote +administrator immediatedly!

+ +
+ +

Place Your Vote Here

+ +

Rank each candidate in order of more preferred to least +preferred. (e.g., 123 or 321 or 213, etc.)

+ +<%= 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 index 0000000..ec93546 --- /dev/null +++ b/lib/uniq_token.rb @@ -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/votes.yml b/test/fixtures/rankings.yml similarity index 100% rename from test/fixtures/votes.yml rename to test/fixtures/rankings.yml diff --git a/test/fixtures/vote.yml b/test/fixtures/vote.yml new file mode 100644 index 0000000..8794d28 --- /dev/null +++ b/test/fixtures/vote.yml @@ -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 index 0000000..992476b --- /dev/null +++ b/test/functional/voter_controller_test.rb @@ -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 index 0000000..2e44da8 --- /dev/null +++ b/test/unit/ranking_test.rb @@ -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 diff --git a/test/unit/votes_test.rb b/test/unit/vote_test.rb similarity index 100% rename from test/unit/votes_test.rb rename to test/unit/vote_test.rb -- 2.30.2