refinerycms-ballots 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/app/controllers/admin/ballots_controller.rb +9 -0
  2. data/app/controllers/ballots_controller.rb +24 -0
  3. data/app/controllers/votes_controller.rb +114 -0
  4. data/app/helpers/admin/ballots_helper.rb +19 -0
  5. data/app/helpers/votes_helper.rb +26 -0
  6. data/app/models/ballot.rb +67 -0
  7. data/app/models/ballot_vote.rb +41 -0
  8. data/app/models/candidate.rb +22 -0
  9. data/app/models/candidate_vote.rb +17 -0
  10. data/app/models/office.rb +20 -0
  11. data/app/models/office_vote.rb +33 -0
  12. data/app/views/admin/ballots/_actions.html.erb +28 -0
  13. data/app/views/admin/ballots/_ballot.html.erb +21 -0
  14. data/app/views/admin/ballots/_ballots.html.erb +2 -0
  15. data/app/views/admin/ballots/_candidate_fields.html.erb +6 -0
  16. data/app/views/admin/ballots/_form.html.erb +40 -0
  17. data/app/views/admin/ballots/_office_fields.html.erb +21 -0
  18. data/app/views/admin/ballots/_records.html.erb +18 -0
  19. data/app/views/admin/ballots/_show_voters.html.erb +6 -0
  20. data/app/views/admin/ballots/_sortable_list.html.erb +7 -0
  21. data/app/views/admin/ballots/edit.html.erb +1 -0
  22. data/app/views/admin/ballots/index.html.erb +10 -0
  23. data/app/views/admin/ballots/new.html.erb +1 -0
  24. data/app/views/admin/ballots/show.html.erb +32 -0
  25. data/app/views/votes/_confirmation_office_votes_display.html.erb +30 -0
  26. data/app/views/votes/confirm.html.erb +34 -0
  27. data/app/views/votes/create.html.erb +15 -0
  28. data/app/views/votes/login.html.erb +24 -0
  29. data/app/views/votes/new.html.erb +41 -0
  30. data/config/locales/en.yml +25 -0
  31. data/config/locales/fr.yml +25 -0
  32. data/config/locales/lolcat.yml +25 -0
  33. data/config/locales/nb.yml +21 -0
  34. data/config/locales/nl.yml +21 -0
  35. data/config/routes.rb +21 -0
  36. data/lib/generators/refinerycms_ballots_generator.rb +6 -0
  37. data/lib/member_extensions.rb +12 -0
  38. data/lib/refinerycms-ballots.rb +30 -0
  39. data/lib/tasks/ballots.rake +13 -0
  40. metadata +105 -0
@@ -0,0 +1,9 @@
1
+ module Admin
2
+ class BallotsController < Admin::BaseController
3
+
4
+ crudify :ballot, :xhr_paging => true
5
+
6
+ end
7
+ end
8
+
9
+
@@ -0,0 +1,24 @@
1
+ class BallotsController < ApplicationController
2
+
3
+ before_filter :find_all_ballots
4
+ before_filter :find_page
5
+
6
+ def index
7
+ redirect_to root_url
8
+ end
9
+
10
+ def show
11
+ redirect_to root_url
12
+ end
13
+
14
+ protected
15
+
16
+ def find_all_ballots
17
+ @ballots = Ballot.order('position ASC')
18
+ end
19
+
20
+ def find_page
21
+ @page = Page.where(:link_url => "/ballots").first
22
+ end
23
+
24
+ end
@@ -0,0 +1,114 @@
1
+ class VotesController < ApplicationController
2
+
3
+ before_filter :find_ballot
4
+ before_filter :find_page
5
+ before_filter :find_member, :except => :login
6
+
7
+ def login
8
+ if @ballot.too_early_to_vote?
9
+ flash[:error] = "This election doesn't start until #{@ballot.pretty_start_date}"
10
+ redirect_to root_url
11
+ elsif @ballot.too_late_to_vote?
12
+ flash[:error] = "This election ended on #{@ballot.pretty_end_date}"
13
+ redirect_to root_url
14
+ else
15
+ present @page
16
+ end
17
+ end
18
+
19
+ # this is basically a state machine to take you through the form
20
+ def proceed
21
+ case params[:commit]
22
+ when /proceed/i then new # login -> new
23
+ when /vote/i then confirm # new -> confirm
24
+ when /back/i then back_to_new # confirm -> new
25
+ when /confirm/i then create # confirm -> create
26
+ else
27
+ logger.error "VotesController#proceed was called with params[:commit]=#{params[:commit].inspect}"
28
+ unspecified_error
29
+ end
30
+ end
31
+
32
+
33
+
34
+ protected
35
+
36
+ def new
37
+ if !@member
38
+ flash[:error] = 'Please double check your number and try again. If it is correct, you may need to contact an admin.'
39
+ redirect_to login_ballot_votes_path(@ballot)
40
+ elsif @member.has_voted_on?(@ballot)
41
+ flash[:error] = "You have already voted on this ballot, revoting isn't allowed"
42
+ redirect_to login_ballot_votes_path(@ballot)
43
+ else
44
+ @ballot_vote = BallotVote.new_for_ballot @ballot
45
+ render :new
46
+ end
47
+ end
48
+
49
+ def confirm
50
+ find_ballot_vote
51
+ if cannot_create_ballot_vote?
52
+ log_cant_create_ballot('new->confirm')
53
+ unspecified_error
54
+ elsif @ballot_vote.valid?
55
+ render :confirm
56
+ else
57
+ render :new
58
+ end
59
+ end
60
+
61
+ def back_to_new
62
+ find_ballot_vote
63
+ render :new
64
+ end
65
+
66
+ def create
67
+ find_ballot_vote
68
+ if can_create_ballot_vote? && @ballot_vote.save
69
+ flash[:notice] = "Thank you for voting."
70
+ redirect_to root_url
71
+ else
72
+ log_cant_create_ballot('confirm->create')
73
+ unspecified_error
74
+ end
75
+ end
76
+
77
+ def unspecified_error
78
+ flash[:error] = "It appears there was a problem, please try again."
79
+ redirect_to login_ballot_votes_path(@ballot)
80
+ end
81
+
82
+ def find_ballot
83
+ @ballot = Ballot.where(:id => params[:ballot_id]).first
84
+ end
85
+
86
+ def find_page
87
+ @page = Page.where(:link_url => "/ballots").first
88
+ end
89
+
90
+ def find_member
91
+ @member = Member.find_by_unique_identifier params[:unique_identifier]
92
+ end
93
+
94
+ def find_ballot_vote
95
+ @ballot_vote = BallotVote.new params[:ballot_vote]
96
+ @ballot_vote.ballot = @ballot
97
+ @ballot_vote.member = @member
98
+ end
99
+
100
+ def cannot_create_ballot_vote?
101
+ !can_create_ballot_vote?
102
+ end
103
+
104
+ def can_create_ballot_vote?
105
+ @member && !@ballot_vote.tampered?
106
+ end
107
+
108
+ def log_cant_create_ballot(path_through_state_machine='')
109
+ logger.error "Ballots #{path_through_state_machine} failed @member=#{@member.inspect}"
110
+ logger.error "Ballots #{path_through_state_machine} failed @ballot_vote.tampered?=#{@ballot_vote.tampered?.inspect}"
111
+ logger.error "Ballots #{path_through_state_machine} failed @ballot_vote.errors=#{@ballot_vote.errors.inspect}"
112
+ end
113
+
114
+ end
@@ -0,0 +1,19 @@
1
+ module Admin
2
+ module BallotsHelper
3
+ def link_to_remove_fields name, f
4
+ link_to_function(name, "remove_fields(this)") + f.hidden_field(:_destroy)
5
+ end
6
+
7
+ def link_to_add_fields(name, f, association)
8
+ new_object = f.object.class.reflect_on_association(association).klass.new
9
+ fields = f.fields_for association, new_object, :child_index => "new_#{association}" do |builder|
10
+ render association.to_s.singularize + "_fields", :f => builder
11
+ end
12
+ raw link_to_function(name, "add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")
13
+ end
14
+
15
+ def position_message_for(n)
16
+ "Vote for #{n}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module VotesHelper
2
+ include Admin::BallotsHelper
3
+ def undervote_message_for(office_vote)
4
+ "you have voted for #{pluralize office_vote.num_votes, 'candidate', 'candidates'}, " \
5
+ "which is fewer than the #{office_vote.max_num_votes} you could have voted for."
6
+ end
7
+
8
+ def undervoted?(office_vote)
9
+ office_vote.num_votes < office_vote.max_num_votes
10
+ end
11
+
12
+ def show_errors_for_ballot_vote(ballot)
13
+ errors = ballot.errors[:base]
14
+ return '' if errors.empty?
15
+ raw "<div class='errorExplanation'><ul>#{errors_as_li errors}</ul></div>"
16
+ end
17
+
18
+ def show_errors_for_office_vote(office)
19
+ return '' if office.errors.empty?
20
+ raw "<div class='errorExplanation'><ul>#{errors_as_li office.errors.full_messages}</ul></div>"
21
+ end
22
+
23
+ def errors_as_li(error_messages)
24
+ error_messages.map { |msg| "<li>#{msg}</li>" }.join
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ class Ballot < ActiveRecord::Base
2
+
3
+ acts_as_indexed :fields => [:title]
4
+ has_many :offices, :dependent => :destroy
5
+ has_many :ballot_votes
6
+ has_many :candidate_votes, :through => :ballot_votes # this will work in rails 3.1, but not 3.0.10, at which time, we can change the implementation of number_of_votes
7
+
8
+ validates :title, :presence => true, :uniqueness => true
9
+ validate :start_date_is_before_end_date
10
+
11
+ accepts_nested_attributes_for :offices,
12
+ :reject_if => lambda { |attrs| attrs[:title].blank? },
13
+ :allow_destroy => true
14
+
15
+ def pretty_start_date
16
+ pretty_date start_date
17
+ end
18
+
19
+ def pretty_end_date
20
+ pretty_date end_date
21
+ end
22
+
23
+ def pretty_date(date)
24
+ date.strftime "%B %e, %Y"
25
+ end
26
+
27
+ def open_for_voting?
28
+ start_date <= today && today <= end_date
29
+ end
30
+
31
+ def too_early_to_vote?
32
+ today < start_date
33
+ end
34
+
35
+ def too_late_to_vote?
36
+ end_date < today
37
+ end
38
+
39
+ def number_of_votes
40
+ sums = ballot_votes.map do |ballot_vote|
41
+ ballot_vote.candidate_votes.where(:voted => true).count
42
+ end
43
+ sums.inject :+
44
+ end
45
+
46
+ def number_of_voters
47
+ ballot_votes.count
48
+ end
49
+
50
+ def voting_members
51
+ Member.all.select { |member| member.has_voted_on? self }
52
+ end
53
+
54
+ def nonvoting_members
55
+ Member.all.reject { |member| member.has_voted_on? self }
56
+ end
57
+
58
+
59
+ def start_date_is_before_end_date
60
+ return if start_date < end_date
61
+ errors[:start_date] << 'must be after end date'
62
+ end
63
+ private
64
+ def today
65
+ Time.now
66
+ end
67
+ end
@@ -0,0 +1,41 @@
1
+ class BallotVote < ActiveRecord::Base
2
+ belongs_to :member
3
+ belongs_to :ballot
4
+ has_many :office_votes
5
+ has_many :candidate_votes, :through => :office_votes
6
+ validates_associated :office_votes
7
+ validate :ballot_must_be_open_for_voting
8
+ validate :member_vote_must_be_unique
9
+
10
+ accepts_nested_attributes_for :office_votes, :allow_destroy => true
11
+
12
+ def self.new_for_ballot(ballot)
13
+ bv = BallotVote.new :ballot => ballot
14
+ ballot.offices.each do |office|
15
+ ov = bv.office_votes.build :office => office
16
+ office.candidates.each do |candidate|
17
+ ov.candidate_votes.build :candidate => candidate
18
+ end
19
+ end
20
+ bv
21
+ end
22
+
23
+ def tampered?
24
+ !ballot || office_votes.any? { |office_vote| office_vote.tampered? ballot }
25
+ end
26
+
27
+ private
28
+ def member_vote_must_be_unique
29
+ count = BallotVote.where(:member_id => member_id, :ballot_id => ballot_id).count
30
+ return if count.zero?
31
+ errors[:base] << "You cannot vote twice"
32
+ end
33
+
34
+ def ballot_must_be_open_for_voting
35
+ if ballot.too_early_to_vote?
36
+ errors[:base] << "This ballot won't open for voting until #{ballot.pretty_start_date}."
37
+ elsif ballot.too_late_to_vote?
38
+ errors[:base] << "This ballot closed for voting on #{ballot.pretty_end_date}."
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ class Candidate < ActiveRecord::Base
2
+
3
+ acts_as_indexed :fields => [:name]
4
+ belongs_to :office
5
+ has_many :candidate_votes
6
+
7
+ validates :name, :presence => true
8
+
9
+ def percentage
10
+ return 0 if number_of_votes.zero?
11
+ 100 * number_of_votes / number_of_voters
12
+ end
13
+
14
+ def number_of_votes
15
+ candidate_votes.where(:voted => true).count
16
+ end
17
+
18
+ def number_of_voters
19
+ candidate_votes.count
20
+ end
21
+
22
+ end
@@ -0,0 +1,17 @@
1
+ class CandidateVote < ActiveRecord::Base
2
+ belongs_to :office_vote
3
+ belongs_to :candidate
4
+
5
+ def name
6
+ candidate && candidate.name
7
+ end
8
+
9
+ def vote
10
+ self.voted = true
11
+ end
12
+
13
+ def tampered?(office)
14
+ !candidate || candidate.office != office
15
+ end
16
+
17
+ end
@@ -0,0 +1,20 @@
1
+ class Office < ActiveRecord::Base
2
+
3
+ acts_as_indexed :fields => [:title]
4
+ has_many :candidates, :dependent => :destroy
5
+ belongs_to :ballot
6
+
7
+ validates :title, :presence => true
8
+
9
+
10
+ validates_numericality_of :number_of_positions, :only_integer => true, :message => "is the number of people who can be elected to this office (an integer)."
11
+
12
+ accepts_nested_attributes_for :candidates,
13
+ :reject_if => lambda { |attrs| attrs[:name].blank? },
14
+ :allow_destroy => true
15
+
16
+ def candidates_by_rating
17
+ candidates.sort_by { |candidate| candidate.number_of_votes }.reverse
18
+ end
19
+
20
+ end
@@ -0,0 +1,33 @@
1
+ class OfficeVote < ActiveRecord::Base
2
+ belongs_to :ballot_vote
3
+ belongs_to :office
4
+ has_many :candidate_votes
5
+ validate :cannot_overvote
6
+
7
+ accepts_nested_attributes_for :candidate_votes, :allow_destroy => true
8
+
9
+ def title
10
+ office && office.title
11
+ end
12
+
13
+ def num_votes
14
+ candidate_votes.select { |cv| cv.voted? }.size
15
+ end
16
+
17
+ def max_num_votes
18
+ (office && office.number_of_positions) || 0
19
+ end
20
+
21
+ def tampered?(ballot)
22
+ !office || office.ballot != ballot || candidate_votes.any? { |candidate_vote| candidate_vote.tampered? office }
23
+ end
24
+
25
+ private
26
+
27
+ def cannot_overvote
28
+ if num_votes > max_num_votes
29
+ errors[:base] << "Cannot vote for #{num_votes}, maximum is #{max_num_votes}"
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,28 @@
1
+ <ul>
2
+ <% if Admin::BallotsController.searchable? %>
3
+ <li>
4
+ <%= render :partial => "/shared/admin/search",
5
+ :locals => {
6
+ :url => admin_ballots_path
7
+ } %>
8
+ </li>
9
+ <% end %>
10
+ <li>
11
+ <%= link_to t('.create_new'), new_admin_ballot_path,
12
+ :class => "add_icon" %>
13
+ </li>
14
+ <% if !searching? and Admin::BallotsController.sortable? and Ballot.count > 1 %>
15
+ <li>
16
+ <%= link_to t('.reorder', :what => "Ballots"),
17
+ admin_ballots_path,
18
+ :id => "reorder_action",
19
+ :class => "reorder_icon" %>
20
+
21
+ <%= link_to t('.reorder_done', :what => "Ballots"),
22
+ admin_ballots_path,
23
+ :id => "reorder_action_done",
24
+ :style => "display: none;",
25
+ :class => "reorder_icon" %>
26
+ </li>
27
+ <% end %>
28
+ </ul>
@@ -0,0 +1,21 @@
1
+ <li class='clearfix record <%= cycle("on", "on-hover") %>' id="<%= dom_id(ballot) -%>">
2
+ <span class='title'>
3
+ <%= ballot.title %>
4
+ <span class="preview">&nbsp;</span>
5
+ </span>
6
+ <span class='vote-link'>
7
+ (<%= link_to 'Link for voters', login_ballot_votes_url(ballot) %>)
8
+ </span>
9
+ <span class='actions'>
10
+ <%= link_to refinery_icon_tag("application_go.png"), admin_ballot_path(ballot),
11
+ :title => t('.view_live_html'),
12
+ :target => "_blank" %>
13
+ <%= link_to refinery_icon_tag("application_edit.png"), edit_admin_ballot_path(ballot),
14
+ :title => t('.edit') %>
15
+ <%= link_to refinery_icon_tag("delete.png"), admin_ballot_path(ballot),
16
+ :class => "cancel confirm-delete",
17
+ :title => t('.delete'),
18
+ :confirm => t('message', :scope => 'shared.admin.delete', :title => ballot.title),
19
+ :method => :delete %>
20
+ </span>
21
+ </li>
@@ -0,0 +1,2 @@
1
+ <%= will_paginate @ballots %>
2
+ <%= render :partial => "sortable_list" %>
@@ -0,0 +1,6 @@
1
+ <p class="fields">
2
+ <%= f.label :name, 'Candidate' %>
3
+ <%= f.text_field :name, :rows => 1 %>
4
+ <%= f.hidden_field :_destroy %>
5
+ <%= link_to_function "remove", "remove_fields(this)" %>
6
+ </p>
@@ -0,0 +1,40 @@
1
+ <% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
2
+ <script src="/javascripts/refinerycms-ballots.js" type="text/javascript"></script>
3
+
4
+ <%= form_for [:admin, @ballot] do |f| -%>
5
+ <%= render :partial => "/shared/admin/error_messages", :locals => {
6
+ :object => @ballot,
7
+ :include_object_name => true
8
+ } %>
9
+
10
+ <div class='field'>
11
+ <%= f.label :title -%>
12
+ <%= f.text_field :title, :class => 'larger widest' -%>
13
+ </div>
14
+
15
+ <div class="field start_date">
16
+ <%= f.label :start_date %><br />
17
+ <%= f.datetime_select :start_date %>
18
+ </div>
19
+
20
+ <div class="field end_date">
21
+ <%= f.label :end_date %><br />
22
+ <%= f.datetime_select :end_date %>
23
+ </div>
24
+
25
+ <% @ballot.offices.build %>
26
+ <%= f.fields_for :offices do |builder| %>
27
+ <%= render 'office_fields', :f => builder %>
28
+ <% end %>
29
+
30
+ <p><%= link_to_add_fields "Add Office", f, :offices %></p>
31
+
32
+ <%= render :partial => "/shared/admin/form_actions",
33
+ :locals => {
34
+ :f => f,
35
+ :continue_editing => false,
36
+ :delete_title => t('delete', :scope => 'admin.ballots.ballot'),
37
+ :delete_confirmation => t('message', :scope => 'shared.admin.delete', :title => @ballot.title)
38
+ } %>
39
+ <% end -%>
40
+
@@ -0,0 +1,21 @@
1
+ <div class="office fields">
2
+ <%=f.label :content, "Office (#{link_to_remove_fields 'remove', f})".html_safe %>
3
+
4
+ <div class="field">
5
+ <%= f.label :title %>
6
+ <%= f.text_field :title, :size => 15 %>
7
+ </div>
8
+
9
+ <div class="field">
10
+ <%= f.label :number_of_positions %>
11
+ <%= f.text_field :number_of_positions, :size => 15 %>
12
+ </div>
13
+
14
+ <% f.object.candidates.build %>
15
+
16
+ <%= f.fields_for :candidates do |builder| %>
17
+ <%= render 'candidate_fields', :f => builder %>
18
+ <% end %>
19
+
20
+ <p><%= link_to_add_fields "Add candidate", f, :candidates %></p>
21
+ </div>
@@ -0,0 +1,18 @@
1
+ <% if searching? %>
2
+ <h2><%= t('results_for', :scope => 'shared.admin.search', :query => params[:search]) %></h2>
3
+ <% end %>
4
+ <% if @ballots.any? %>
5
+ <div class='pagination_container'>
6
+ <%= render :partial => 'ballots' %>
7
+ </div>
8
+ <% else %>
9
+ <p>
10
+ <% unless searching? %>
11
+ <strong>
12
+ <%= t('.no_items_yet') %>
13
+ </strong>
14
+ <% else %>
15
+ <%= t('no_results', :scope => 'shared.admin.search') %>
16
+ <% end %>
17
+ </p>
18
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <h4><%= title %></h4>
2
+ <ul>
3
+ <% voters.each do |voter| %>
4
+ <li><%= voter.first_name %> <%= voter.last_name %></li>
5
+ <% end %>
6
+ </ul>
@@ -0,0 +1,7 @@
1
+ <ul id='sortable_list'>
2
+ <%= render :partial => 'ballot', :collection => @ballots %>
3
+ </ul>
4
+ <%= render :partial => "/shared/admin/sortable_list",
5
+ :locals => {
6
+ :continue_reordering => (local_assigns.keys.include?(:continue_reordering)) ? continue_reordering : true
7
+ } %>
@@ -0,0 +1 @@
1
+ <%= render :partial => "form" %>
@@ -0,0 +1,10 @@
1
+ <section id='records'>
2
+ <%= render :partial => 'records' %>
3
+ </section>
4
+ <aside id='actions'>
5
+ <%= render :partial => 'actions' %>
6
+ </aside>
7
+ <%= render :partial => '/shared/admin/make_sortable',
8
+ :locals => {
9
+ :tree => false
10
+ } if !searching? and Admin::BallotsController.sortable? and Ballot.count > 1 %>
@@ -0,0 +1 @@
1
+ <%= render :partial => "form" %>
@@ -0,0 +1,32 @@
1
+ <% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
2
+
3
+ <% content_for :body_content_title do %>
4
+ <%= @ballot.title %>
5
+ <% end %>
6
+
7
+
8
+ <% content_for :body_content_left do %>
9
+ <section class="ballot left-column">
10
+ <% @ballot.offices.each do |office| %>
11
+ <h4><%= office.title %></h4>
12
+ <ol class="candidates">
13
+ <% office.candidates_by_rating.each do |candidate| %>
14
+ <li>
15
+ <%= candidate.name %>
16
+ <%= candidate.percentage %>% --
17
+ <%= pluralize candidate.number_of_votes, 'vote', 'votes' %>
18
+ </li>
19
+ <% end %>
20
+ </ol>
21
+ <% end %>
22
+ </section>
23
+
24
+
25
+ <section class="right-column">
26
+ <%= render 'show_voters', :voters => @ballot.voting_members, :title => 'Members who have voted' %>
27
+ <%= render 'show_voters', :voters => @ballot.nonvoting_members, :title => 'Members who have not voted' %>
28
+ </section>
29
+ <% end %>
30
+
31
+
32
+ <%= render :partial => "/shared/content_page" %>
@@ -0,0 +1,30 @@
1
+ <h4><%= office_vote.office.title %></h4>
2
+
3
+ <% candidate_votes = office_vote.candidate_votes.select(&:voted) %>
4
+
5
+
6
+ <% if candidate_votes.size.zero? %>
7
+ <div class="errorExplanation warningExplanation">
8
+ <ul><li>You did not vote.</li></ul>
9
+ </div>
10
+
11
+ <ol class="candidates">
12
+ </ol>
13
+ <% else %>
14
+
15
+ <% if undervoted? office_vote %>
16
+ <div class="errorExplanation warningExplanation">
17
+ <ul>
18
+ <li><%= undervote_message_for office_vote %></li>
19
+ </ul>
20
+ </div>
21
+ <% end %>
22
+
23
+ <ol class="candidates">
24
+ <% candidate_votes.each do |candidate_vote| %>
25
+ <li><%= candidate_vote.name %></li>
26
+ <% end %>
27
+ </ol>
28
+ <% end %>
29
+
30
+
@@ -0,0 +1,34 @@
1
+ <% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
2
+ <% content_for :body_content_title do %>
3
+ Confirm <%= @ballot.title %>
4
+ <% end %>
5
+
6
+
7
+ <% content_for :body_content_left do %>
8
+
9
+ <!-- the displayed results -->
10
+ <section class="ballot_vote">
11
+ <% @ballot_vote.office_votes.each do |office_vote| %>
12
+ <%= render 'confirmation_office_votes_display', :office_vote => office_vote %>
13
+ <% end %>
14
+ </section>
15
+
16
+
17
+ <!-- the full hidden data -->
18
+ <%= form_for @ballot_vote, :url => { :action => "proceed" } do |f| -%>
19
+ <%= text_field_tag :unique_identifier, @member.unique_identifier, :hidden => true %>
20
+ <%= f.fields_for :office_votes do |office_vote_form| %>
21
+ <%= office_vote_form.text_field :office_id, :hidden => true %>
22
+ <%= office_vote_form.fields_for :candidate_votes do |candidate_vote_form| %>
23
+ <%= candidate_vote_form.text_field :candidate_id, :hidden => true %>
24
+ <%= candidate_vote_form.check_box :voted, :checked => candidate_vote_form.object.voted, :hidden => true %>
25
+ <% end %>
26
+ <% end %>
27
+ <div class="field"><%= submit_tag 'Back' %>
28
+ <%= submit_tag 'Confirm' %></div>
29
+ <% end %>
30
+ <% end %>
31
+
32
+
33
+ <%= render :partial => "/shared/content_page" %>
34
+
@@ -0,0 +1,15 @@
1
+ <% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
2
+ <% content_for :body_content_title do %>
3
+ Success
4
+ <% end %>
5
+
6
+ <% content_for :body_content_left do %>
7
+ <section class="ballot">
8
+ <p>
9
+ Your vote has been recorded. <%= link_to 'Back to ballot', @ballot %>.
10
+ </p>
11
+ </section>
12
+ <% end %>
13
+
14
+
15
+ <%= render :partial => "/shared/content_page" %>
@@ -0,0 +1,24 @@
1
+ <%= form_tag proceed_ballot_votes_path(@ballot), :method => 'post' do %>
2
+
3
+ <fieldset>
4
+
5
+ <div class="field">
6
+ <%= label_tag :first_name %>
7
+ <%= text_field_tag :first_name %><br />
8
+ </div>
9
+
10
+ <div class="field">
11
+ <%= label_tag :last_name %>
12
+ <%= text_field_tag :last_name %><br />
13
+ </div>
14
+
15
+ <div class="field">
16
+ <%= label_tag :unique_identifier %>
17
+   <%= text_field_tag :unique_identifier %><br />
18
+ </div>
19
+
20
+ </fieldset>
21
+
22
+
23
+   <div class="field"><%= submit_tag "Proceed to ballot" %></div>
24
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
2
+ <% content_for :body_content_title do %>
3
+ <%= @ballot.title %>
4
+ <% end %>
5
+
6
+ <% content_for :body_content_left do %>
7
+ <section class="ballot">
8
+ <p><%= @ballot.pretty_start_date %> through <%= @ballot.pretty_end_date %></p>
9
+
10
+ <%= form_for @ballot_vote, :url => { :action => "proceed" } do |f| -%>
11
+ <%= show_errors_for_ballot_vote @ballot_vote %>
12
+
13
+ <%= text_field_tag :unique_identifier, @member.unique_identifier, :hidden => true %>
14
+ <%= f.fields_for :office_votes do |office_vote_form| %>
15
+ <% office_vote = office_vote_form.object %>
16
+
17
+ <fieldset>
18
+ <h4><%= office_vote.title %></h4>
19
+ <%= show_errors_for_office_vote office_vote %>
20
+ <%= office_vote_form.text_field :office_id, :hidden => true %>
21
+ <p><%= position_message_for office_vote.max_num_votes %></p>
22
+ <ol class="candidates">
23
+ <%= office_vote_form.fields_for :candidate_votes do |candidate_vote_form| %>
24
+ <li class="field">
25
+ <%= candidate_vote_form.text_field :candidate_id, :hidden => true %>
26
+ <%= candidate_vote_form.check_box :voted, :checked => candidate_vote_form.object.voted %>
27
+ <%= candidate_vote_form.label :voted, candidate_vote_form.object.name %><br />
28
+ </li>
29
+ <% end %>
30
+ </ol>
31
+ </fieldset>
32
+
33
+ <% end %>
34
+
35
+ <div class="field"><%= submit_tag 'Vote' %></div>
36
+ <% end %>
37
+ </section>
38
+ <% end %>
39
+
40
+
41
+ <%= render :partial => "/shared/content_page" %>
@@ -0,0 +1,25 @@
1
+ en:
2
+ shared:
3
+ admin:
4
+ image_picker:
5
+ image: image
6
+ plugins:
7
+ ballots:
8
+ title: Ballots
9
+ admin:
10
+ ballots:
11
+ actions:
12
+ create_new: Add New Ballot
13
+ reorder: Reorder Ballots
14
+ reorder_done: Done Reordering Ballots
15
+ records:
16
+ title: Ballots
17
+ sorry_no_results: Sorry! There are no results found.
18
+ no_items_yet: There are no Ballots yet. Click "Add New Ballot" to add your first ballot.
19
+ ballot:
20
+ view_live_html: View results.
21
+ edit: Edit this ballot
22
+ delete: Remove this ballot forever
23
+ ballots:
24
+ show:
25
+ other: Other Ballots
@@ -0,0 +1,25 @@
1
+ fr:
2
+ shared:
3
+ admin:
4
+ image_picker:
5
+ image: image
6
+ plugins:
7
+ ballots:
8
+ title: Ballots
9
+ admin:
10
+ ballots:
11
+ actions:
12
+ create_new: Créer un(e) nouve(au/l/lle) Ballot
13
+ reorder: Réordonner les Ballots
14
+ reorder_done: Fin de réordonnancement des Ballots
15
+ records:
16
+ title: Ballots
17
+ sorry_no_results: "Désolé ! Aucun résultat."
18
+ no_items_yet: 'Il n''y a actuellement aucun(e) Ballot. Cliquer sur "Créer un(e) nouve(au/l/lle) Ballot" pour créer votre premi(er/ère) ballot.'
19
+ ballot:
20
+ view_live_html: Voir ce(t/tte) ballot <br/><em>(Ouvre une nouvelle fenêtre)</em>
21
+ edit: Modifier ce(t/tte) ballot
22
+ delete: Supprimer définitivement ce(t/tte) ballot
23
+ ballots:
24
+ show:
25
+ other: Autres Ballots
@@ -0,0 +1,25 @@
1
+ lolcat:
2
+ shared:
3
+ admin:
4
+ image_picker:
5
+ image: IMAGE
6
+ plugins:
7
+ ballots:
8
+ title: Ballots
9
+ admin:
10
+ ballots:
11
+ actions:
12
+ create_new: CREATE NEW Ballot
13
+ reorder: REORDR Ballots
14
+ reorder_done: DUN REORDERIN Ballots
15
+ records:
16
+ title: Ballots
17
+ sorry_no_results: SRY! THAR R NO RESULTS FINDZ.
18
+ no_items_yet: THAR R NO Ballots YET. CLICK "CREATE NEW Ballot" 2 ADD UR FURST ballot.
19
+ ballot:
20
+ view_live_html: VIEW DIS ballot LIV <BR/><EM>(OPENS IN NEW WINDOW)</EM>
21
+ edit: EDIT DIS ballot
22
+ delete: REMOOV DIS ballot FOREVR
23
+ ballots:
24
+ show:
25
+ other: OTHR Ballots
@@ -0,0 +1,21 @@
1
+ nb:
2
+ plugins:
3
+ ballots:
4
+ title: Ballots
5
+ admin:
6
+ ballots:
7
+ actions:
8
+ create_new: Lag en ny Ballot
9
+ reorder: Endre rekkefølgen på Ballots
10
+ reorder_done: Ferdig å endre rekkefølgen Ballots
11
+ records:
12
+ title: Ballots
13
+ sorry_no_results: Beklager! Vi fant ikke noen resultater.
14
+ no_items_yet: Det er ingen Ballots enda. Klikk på "Lag en ny Ballot" for å legge til din første ballot.
15
+ ballot:
16
+ view_live_html: Vis hvordan denne ballot ser ut offentlig <br/><em>(åpner i et nytt vindu)</em>
17
+ edit: Rediger denne ballot
18
+ delete: Fjern denne ballot permanent
19
+ ballots:
20
+ show:
21
+ other: Andre Ballots
@@ -0,0 +1,21 @@
1
+ nl:
2
+ plugins:
3
+ ballots:
4
+ title: Ballots
5
+ admin:
6
+ ballots:
7
+ actions:
8
+ create_new: Maak een nieuwe Ballot
9
+ reorder: Wijzig de volgorde van de Ballots
10
+ reorder_done: Klaar met het wijzingen van de volgorde van de Ballots
11
+ records:
12
+ title: Ballots
13
+ sorry_no_results: Helaas! Er zijn geen resultaten gevonden.
14
+ no_items_yet: Er zijn nog geen Ballots. Druk op 'Maak een nieuwe Ballot' om de eerste aan te maken.
15
+ ballot:
16
+ view_live_html: Bekijk deze ballot op de website <br/><em>(opent een nieuw venster)</em>
17
+ edit: Bewerk deze ballot
18
+ delete: Verwijder deze ballot voor eeuwig
19
+ ballots:
20
+ show:
21
+ other: Andere Ballots
data/config/routes.rb ADDED
@@ -0,0 +1,21 @@
1
+ ::Refinery::Application.routes.draw do
2
+
3
+ resources :ballots, :only => :index do
4
+ resources :votes, :only => [] do
5
+ collection do
6
+ get :login
7
+ post :proceed
8
+ get :proceed, :action => :login
9
+ end
10
+ end
11
+ end
12
+
13
+
14
+ scope(:path => 'refinery', :as => 'admin', :module => 'admin') do
15
+ resources :ballots do
16
+ collection do
17
+ post :update_positions
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ class RefinerycmsBallots < Refinery::Generators::EngineInstaller
2
+
3
+ source_root File.expand_path('../../../', __FILE__)
4
+ engine_name "ballots"
5
+
6
+ end
@@ -0,0 +1,12 @@
1
+ class Member < ActiveRecord::Base
2
+ has_many :ballot_votes
3
+
4
+ def number_of_times_voted
5
+ ballot_votes.count
6
+ end
7
+
8
+ def has_voted_on?(ballot)
9
+ ballot_votes.where(:ballot_id => ballot.id).any?
10
+ end
11
+ end
12
+
@@ -0,0 +1,30 @@
1
+ require 'refinerycms-base'
2
+ require 'member_extensions'
3
+
4
+ module Refinery
5
+ module Ballots
6
+
7
+ class << self
8
+ attr_accessor :root
9
+ def root
10
+ @root ||= Pathname.new(File.expand_path('../../', __FILE__))
11
+ end
12
+ end
13
+
14
+ class Engine < Rails::Engine
15
+ initializer "static assets" do |app|
16
+ app.middleware.insert_after ::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/public"
17
+ end
18
+
19
+ config.after_initialize do
20
+ Refinery::Plugin.register do |plugin|
21
+ plugin.name = "ballots"
22
+ plugin.pathname = root
23
+ plugin.activity = {
24
+ :class => Ballot
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ namespace :refinery do
2
+
3
+ namespace :ballots do
4
+
5
+ # call this task my running: rake refinery:ballots:my_task
6
+ # desc "Description of my task below"
7
+ # task :my_task => :environment do
8
+ # # add your logic here
9
+ # end
10
+
11
+ end
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: refinerycms-ballots
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josh Cheek
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: refinerycms-simple_members
16
+ requirement: &70326298798500 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70326298798500
25
+ - !ruby/object:Gem::Dependency
26
+ name: factory_girl
27
+ requirement: &70326298812240 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70326298812240
36
+ description: Ruby on Rails Ballots engine for Refinery CMS
37
+ email:
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - lib/generators/refinerycms_ballots_generator.rb
43
+ - lib/member_extensions.rb
44
+ - lib/refinerycms-ballots.rb
45
+ - lib/tasks/ballots.rake
46
+ - config/locales/en.yml
47
+ - config/locales/fr.yml
48
+ - config/locales/lolcat.yml
49
+ - config/locales/nb.yml
50
+ - config/locales/nl.yml
51
+ - config/routes.rb
52
+ - app/controllers/admin/ballots_controller.rb
53
+ - app/controllers/ballots_controller.rb
54
+ - app/controllers/votes_controller.rb
55
+ - app/helpers/admin/ballots_helper.rb
56
+ - app/helpers/votes_helper.rb
57
+ - app/models/ballot.rb
58
+ - app/models/ballot_vote.rb
59
+ - app/models/candidate.rb
60
+ - app/models/candidate_vote.rb
61
+ - app/models/office.rb
62
+ - app/models/office_vote.rb
63
+ - app/views/admin/ballots/_actions.html.erb
64
+ - app/views/admin/ballots/_ballot.html.erb
65
+ - app/views/admin/ballots/_ballots.html.erb
66
+ - app/views/admin/ballots/_candidate_fields.html.erb
67
+ - app/views/admin/ballots/_form.html.erb
68
+ - app/views/admin/ballots/_office_fields.html.erb
69
+ - app/views/admin/ballots/_records.html.erb
70
+ - app/views/admin/ballots/_show_voters.html.erb
71
+ - app/views/admin/ballots/_sortable_list.html.erb
72
+ - app/views/admin/ballots/edit.html.erb
73
+ - app/views/admin/ballots/index.html.erb
74
+ - app/views/admin/ballots/new.html.erb
75
+ - app/views/admin/ballots/show.html.erb
76
+ - app/views/votes/_confirmation_office_votes_display.html.erb
77
+ - app/views/votes/confirm.html.erb
78
+ - app/views/votes/create.html.erb
79
+ - app/views/votes/login.html.erb
80
+ - app/views/votes/new.html.erb
81
+ homepage:
82
+ licenses: []
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.10
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Ballots engine for Refinery CMS
105
+ test_files: []