refinerycms-ballots 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []