Allow blank votes in polls via web
With the old interface, there wasn't a clear way to send a blank ballot. But now that we've got a form, there's an easy way: clicking on "Vote" while leaving the form blank.
This commit is contained in:
@@ -2,8 +2,12 @@
|
|||||||
<% if voted_in_booth? %>
|
<% if voted_in_booth? %>
|
||||||
<%= callout(t("polls.show.already_voted_in_booth")) %>
|
<%= callout(t("polls.show.already_voted_in_booth")) %>
|
||||||
<% elsif voted_in_web? %>
|
<% elsif voted_in_web? %>
|
||||||
|
<% if voted_blank? %>
|
||||||
|
<%= callout(t("polls.show.already_voted_blank_in_web")) %>
|
||||||
|
<% else %>
|
||||||
<%= callout(t("polls.show.already_voted_in_web")) %>
|
<%= callout(t("polls.show.already_voted_in_web")) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% if current_user.nil? %>
|
<% if current_user.nil? %>
|
||||||
<%= callout(not_logged_in_text, html_class: "primary") %>
|
<%= callout(not_logged_in_text, html_class: "primary") %>
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ class Polls::CalloutComponent < ApplicationComponent
|
|||||||
poll.voted_in_web?(current_user)
|
poll.voted_in_web?(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def voted_blank?
|
||||||
|
poll.answers.where(author: current_user).none?
|
||||||
|
end
|
||||||
|
|
||||||
def callout(text, html_class: "warning")
|
def callout(text, html_class: "warning")
|
||||||
tag.div(text, class: "callout #{html_class}")
|
tag.div(text, class: "callout #{html_class}")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ class PollsController < ApplicationController
|
|||||||
@web_vote = Poll::WebVote.new(@poll, current_user)
|
@web_vote = Poll::WebVote.new(@poll, current_user)
|
||||||
|
|
||||||
if @web_vote.update(answer_params)
|
if @web_vote.update(answer_params)
|
||||||
|
if answer_params.blank?
|
||||||
|
redirect_to @poll, notice: t("flash.actions.create.poll_voter_blank")
|
||||||
|
else
|
||||||
redirect_to @poll, notice: t("flash.actions.create.poll_voter")
|
redirect_to @poll, notice: t("flash.actions.create.poll_voter")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
@comment_tree = CommentTree.new(@poll, params[:page], @current_order)
|
@comment_tree = CommentTree.new(@poll, params[:page], @current_order)
|
||||||
render :show
|
render :show
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class Poll < ApplicationRecord
|
|||||||
has_many :officer_assignments, through: :booth_assignments
|
has_many :officer_assignments, through: :booth_assignments
|
||||||
has_many :officers, through: :officer_assignments
|
has_many :officers, through: :officer_assignments
|
||||||
has_many :questions, inverse_of: :poll, dependent: :destroy
|
has_many :questions, inverse_of: :poll, dependent: :destroy
|
||||||
|
has_many :answers, through: :questions
|
||||||
has_many :comments, as: :commentable, inverse_of: :commentable
|
has_many :comments, as: :commentable, inverse_of: :commentable
|
||||||
has_many :ballot_sheets
|
has_many :ballot_sheets
|
||||||
|
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ class Poll::Stats
|
|||||||
end
|
end
|
||||||
|
|
||||||
def total_web_valid
|
def total_web_valid
|
||||||
voters.where(origin: "web").count - total_web_white
|
voters.where(origin: "web", user_id: poll.answers.select(:author_id).distinct).count
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_web_white
|
def total_web_white
|
||||||
0
|
voters.where(origin: "web").count - total_web_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_web_null
|
def total_web_null
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ class Poll::WebVote
|
|||||||
all_valid = true
|
all_valid = true
|
||||||
|
|
||||||
user.with_lock do
|
user.with_lock do
|
||||||
unless questions.any? { |question| params.dig(question.id.to_s, :option_id).present? }
|
|
||||||
Poll::Voter.find_by(user: user, poll: poll, origin: "web")&.destroy!
|
|
||||||
end
|
|
||||||
|
|
||||||
questions.each do |question|
|
questions.each do |question|
|
||||||
question.answers.where(author: user).destroy_all
|
question.answers.where(author: user).destroy_all
|
||||||
next unless params[question.id.to_s]
|
next unless params[question.id.to_s]
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ en:
|
|||||||
show:
|
show:
|
||||||
already_voted_in_booth: "You have already participated in a physical booth. You can not participate again."
|
already_voted_in_booth: "You have already participated in a physical booth. You can not participate again."
|
||||||
already_voted_in_web: "You have already participated in this poll. If you vote again it will be overwritten."
|
already_voted_in_web: "You have already participated in this poll. If you vote again it will be overwritten."
|
||||||
|
already_voted_blank_in_web: "You have already participated in this poll by casting a blank vote. If you vote again it will be overwritten."
|
||||||
back: Back to voting
|
back: Back to voting
|
||||||
cant_answer_not_logged_in: "You must %{signin} or %{signup} to participate."
|
cant_answer_not_logged_in: "You must %{signin} or %{signup} to participate."
|
||||||
comments_tab: Comments
|
comments_tab: Comments
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ en:
|
|||||||
poll_question_option_video: "Video created successfully"
|
poll_question_option_video: "Video created successfully"
|
||||||
poll_question_option_image: "Image uploaded successfully"
|
poll_question_option_image: "Image uploaded successfully"
|
||||||
poll_voter: "Thank you for voting!"
|
poll_voter: "Thank you for voting!"
|
||||||
|
poll_voter_blank: "Thank you for voting! Your vote has been registered as a blank vote."
|
||||||
proposal: "Proposal created successfully."
|
proposal: "Proposal created successfully."
|
||||||
proposal_notification: "Your message has been sent correctly."
|
proposal_notification: "Your message has been sent correctly."
|
||||||
budget_investment: "Budget Investment created successfully."
|
budget_investment: "Budget Investment created successfully."
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ es:
|
|||||||
show:
|
show:
|
||||||
already_voted_in_booth: "Ya has participado en esta votación en urnas presenciales, no puedes volver a participar."
|
already_voted_in_booth: "Ya has participado en esta votación en urnas presenciales, no puedes volver a participar."
|
||||||
already_voted_in_web: "Ya has participado en esta votación. Si vuelves a votar se sobreescribirá tu resultado anterior."
|
already_voted_in_web: "Ya has participado en esta votación. Si vuelves a votar se sobreescribirá tu resultado anterior."
|
||||||
|
already_voted_blank_in_web: "Ya has participado en esta votación mediante un voto en blanco. Si vuelves a votar se sobreescribirá tu resultado anterior."
|
||||||
back: Volver a votaciones
|
back: Volver a votaciones
|
||||||
cant_answer_not_logged_in: "Necesitas %{signin} o %{signup} para participar."
|
cant_answer_not_logged_in: "Necesitas %{signin} o %{signup} para participar."
|
||||||
comments_tab: Comentarios
|
comments_tab: Comentarios
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ es:
|
|||||||
poll_question_option_video: "Vídeo creado correctamente"
|
poll_question_option_video: "Vídeo creado correctamente"
|
||||||
poll_question_option_image: "Imagen cargada correctamente"
|
poll_question_option_image: "Imagen cargada correctamente"
|
||||||
poll_voter: "¡Gracias por votar!"
|
poll_voter: "¡Gracias por votar!"
|
||||||
|
poll_voter_blank: "¡Gracias por votar! Tu voto se ha contabilizado como en blanco."
|
||||||
proposal: "Propuesta creada correctamente."
|
proposal: "Propuesta creada correctamente."
|
||||||
proposal_notification: "Tu mensaje ha sido enviado correctamente."
|
proposal_notification: "Tu mensaje ha sido enviado correctamente."
|
||||||
budget_investment: "Proyecto de gasto creado correctamente."
|
budget_investment: "Proyecto de gasto creado correctamente."
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ describe Poll::Stats do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "total participants" do
|
describe "total participants" do
|
||||||
before { allow(stats).to receive(:total_web_white).and_return(1) }
|
|
||||||
|
|
||||||
it "supports every channel" do
|
it "supports every channel" do
|
||||||
3.times { create(:poll_voter, :from_web, poll: poll) }
|
3.times { create(:poll_voter, :from_web, poll: poll) }
|
||||||
create(:poll_recount, :from_booth, poll: poll,
|
create(:poll_recount, :from_booth, poll: poll,
|
||||||
@@ -49,15 +47,29 @@ describe Poll::Stats do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "#total_web_valid" do
|
describe "#total_web_valid" do
|
||||||
before { allow(stats).to receive(:total_web_white).and_return(1) }
|
it "returns only votes containing answers" do
|
||||||
|
question = create(:poll_question, :yes_no, poll: poll)
|
||||||
|
|
||||||
it "returns only valid votes" do
|
2.times do
|
||||||
3.times { create(:poll_voter, :from_web, poll: poll) }
|
voter = create(:poll_voter, :from_web, poll: poll)
|
||||||
|
create(:poll_answer, author: voter.user, question: question)
|
||||||
|
end
|
||||||
|
create(:poll_voter, :from_web, poll: poll)
|
||||||
|
|
||||||
expect(stats.total_web_valid).to eq(2)
|
expect(stats.total_web_valid).to eq(2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#total_web_white" do
|
||||||
|
it "returns voters with no answers" do
|
||||||
|
question = create(:poll_question, :yes_no, poll: poll)
|
||||||
|
3.times { create(:poll_voter, :from_web, poll: poll) }
|
||||||
|
create(:poll_answer, author: poll.voters.last.user, question: question)
|
||||||
|
|
||||||
|
expect(stats.total_web_white).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#total_web_null" do
|
describe "#total_web_null" do
|
||||||
it "returns 0" do
|
it "returns 0" do
|
||||||
expect(stats.total_web_null).to eq(0)
|
expect(stats.total_web_null).to eq(0)
|
||||||
@@ -93,8 +105,8 @@ describe Poll::Stats do
|
|||||||
|
|
||||||
describe "valid percentage by channel" do
|
describe "valid percentage by channel" do
|
||||||
it "is relative to the total amount of valid votes" do
|
it "is relative to the total amount of valid votes" do
|
||||||
|
allow(stats).to receive(:total_web_valid).and_return(1)
|
||||||
create(:poll_recount, :from_booth, poll: poll, total_amount: 2)
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 2)
|
||||||
create(:poll_voter, :from_web, poll: poll)
|
|
||||||
|
|
||||||
expect(stats.valid_percentage_web).to eq(33.333)
|
expect(stats.valid_percentage_web).to eq(33.333)
|
||||||
expect(stats.valid_percentage_booth).to eq(66.667)
|
expect(stats.valid_percentage_booth).to eq(66.667)
|
||||||
@@ -123,7 +135,7 @@ describe Poll::Stats do
|
|||||||
|
|
||||||
describe "#total_valid_votes" do
|
describe "#total_valid_votes" do
|
||||||
it "counts valid votes from every channel" do
|
it "counts valid votes from every channel" do
|
||||||
2.times { create(:poll_voter, :from_web, poll: poll) }
|
allow(stats).to receive(:total_web_valid).and_return(2)
|
||||||
create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 10)
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 10)
|
||||||
create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 20)
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 20)
|
||||||
|
|
||||||
@@ -150,10 +162,9 @@ describe Poll::Stats do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "total percentage by type" do
|
describe "total percentage by type" do
|
||||||
before { allow(stats).to receive(:total_web_white).and_return(1) }
|
before { allow(stats).to receive_messages(total_web_white: 1, total_web_valid: 2) }
|
||||||
|
|
||||||
it "is relative to the total amount of votes" do
|
it "is relative to the total amount of votes" do
|
||||||
3.times { create(:poll_voter, :from_web, poll: poll) }
|
|
||||||
create(:poll_recount, :from_booth, poll: poll,
|
create(:poll_recount, :from_booth, poll: poll,
|
||||||
total_amount: 8,
|
total_amount: 8,
|
||||||
white_amount: 5,
|
white_amount: 5,
|
||||||
|
|||||||
@@ -55,20 +55,21 @@ describe Poll::WebVote do
|
|||||||
expect(question.answers).to be_blank
|
expect(question.answers).to be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not create voters or answers when leaving everything blank" do
|
it "creates a voter but does not create answers when leaving everything blank" do
|
||||||
web_vote.update({})
|
web_vote.update({})
|
||||||
|
|
||||||
expect(poll.reload.voters.size).to eq 0
|
expect(poll.reload.voters.size).to eq 1
|
||||||
expect(question.reload.answers.size).to eq 0
|
expect(question.reload.answers.size).to eq 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "deletes existing answers and voter when no answers are given" do
|
it "deletes existing answers but keeps voters when no answers are given" do
|
||||||
create(:poll_answer, question: question, author: user, option: option_yes)
|
create(:poll_answer, question: question, author: user, option: option_yes)
|
||||||
create(:poll_voter, poll: poll, user: user)
|
create(:poll_voter, poll: poll, user: user)
|
||||||
|
|
||||||
web_vote.update({})
|
web_vote.update({})
|
||||||
|
|
||||||
expect(poll.reload.voters.size).to eq 0
|
expect(poll.reload.voters.size).to eq 1
|
||||||
|
expect(poll.voters.first.user).to eq user
|
||||||
expect(question.reload.answers.size).to eq 0
|
expect(question.reload.answers.size).to eq 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -247,8 +247,9 @@ describe "Polls" do
|
|||||||
within_fieldset("Which ones are better?") { uncheck "Answer A" }
|
within_fieldset("Which ones are better?") { uncheck "Answer A" }
|
||||||
click_button "Vote"
|
click_button "Vote"
|
||||||
|
|
||||||
expect(page).to have_content "Thank you for voting!"
|
expect(page).to have_content "Thank you for voting! Your vote has been registered as a blank vote."
|
||||||
expect(page).not_to have_content "You have already participated"
|
expect(page).to have_content "You have already participated in this poll by casting a blank vote. " \
|
||||||
|
"If you vote again it will be overwritten."
|
||||||
|
|
||||||
within_fieldset("Which ones are better?") do
|
within_fieldset("Which ones are better?") do
|
||||||
expect(page).to have_field type: :checkbox, checked: false, count: 3
|
expect(page).to have_field type: :checkbox, checked: false, count: 3
|
||||||
|
|||||||
Reference in New Issue
Block a user