diff --git a/app/components/shared/in_favor_against_component.html.erb b/app/components/shared/in_favor_against_component.html.erb
index c4c403649..954c10ddb 100644
--- a/app/components/shared/in_favor_against_component.html.erb
+++ b/app/components/shared/in_favor_against_component.html.erb
@@ -4,7 +4,7 @@
title: t("votes.agree"),
"aria-label": agree_aria_label,
"aria-pressed": pressed?("yes"),
- method: "post",
+ method: user_already_voted_with("yes") ? "delete" : "post",
remote: true do %>
<%= t("votes.agree") %>
<% end %>
@@ -16,7 +16,7 @@
title: t("votes.disagree"),
"aria-label": disagree_aria_label,
"aria-pressed": pressed?("no"),
- method: "post",
+ method: user_already_voted_with("no") ? "delete" : "post",
remote: true do %>
<%= t("votes.disagree") %>
<% end %>
diff --git a/app/components/shared/in_favor_against_component.rb b/app/components/shared/in_favor_against_component.rb
index 0c0736407..68e96e99b 100644
--- a/app/components/shared/in_favor_against_component.rb
+++ b/app/components/shared/in_favor_against_component.rb
@@ -28,10 +28,31 @@ class Shared::InFavorAgainstComponent < ApplicationComponent
end
def vote_in_favor_against_path(value)
- if votable.class.name == "Debate"
- debate_votes_path(votable, value: value)
+ if user_already_voted_with(value)
+ remove_vote_path(value)
else
- legislation_process_proposal_votes_path(votable.process, votable, value: value)
+ if votable.class.name == "Debate"
+ debate_votes_path(votable, value: value)
+ else
+ legislation_process_proposal_votes_path(votable.process, votable, value: value)
+ end
end
end
+
+ def user_already_voted_with(value)
+ current_user&.voted_as_when_voted_for(votable) == parse_vote(value)
+ end
+
+ def remove_vote_path(value)
+ vote = votable.votes_for.find_by!(voter: current_user)
+ if votable.class.name == "Debate"
+ debate_vote_path(votable, vote, value: value)
+ else
+ legislation_process_proposal_vote_path(votable.process, votable, vote, value: value)
+ end
+ end
+
+ def parse_vote(value)
+ value == "yes" ? true : false
+ end
end
diff --git a/app/controllers/debates/votes_controller.rb b/app/controllers/debates/votes_controller.rb
index e0b92c472..ae0cf33d5 100644
--- a/app/controllers/debates/votes_controller.rb
+++ b/app/controllers/debates/votes_controller.rb
@@ -2,6 +2,7 @@ module Debates
class VotesController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource :debate
+ load_and_authorize_resource through: :debate, through_association: :votes_for, only: :destroy
def create
authorize! :create, Vote.new(voter: current_user, votable: @debate)
@@ -11,5 +12,13 @@ module Debates
format.js { render :show }
end
end
+
+ def destroy
+ @debate.unvote_by(current_user)
+
+ respond_to do |format|
+ format.js { render :show }
+ end
+ end
end
end
diff --git a/app/controllers/legislation/proposals/votes_controller.rb b/app/controllers/legislation/proposals/votes_controller.rb
index 03886c41c..e51d97db5 100644
--- a/app/controllers/legislation/proposals/votes_controller.rb
+++ b/app/controllers/legislation/proposals/votes_controller.rb
@@ -4,6 +4,7 @@ module Legislation
before_action :authenticate_user!
load_and_authorize_resource :process, class: "Legislation::Process"
load_and_authorize_resource :proposal, class: "Legislation::Proposal", through: :process
+ load_and_authorize_resource through: :proposal, through_association: :votes_for, only: :destroy
def create
authorize! :create, Vote.new(voter: current_user, votable: @proposal)
@@ -13,6 +14,14 @@ module Legislation
format.js { render :show }
end
end
+
+ def destroy
+ @proposal.unvote_by(current_user)
+
+ respond_to do |format|
+ format.js { render :show }
+ end
+ end
end
end
end
diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb
index 53378b4be..f4493840b 100644
--- a/app/models/abilities/common.rb
+++ b/app/models/abilities/common.rb
@@ -81,14 +81,14 @@ module Abilities
can [:create, :destroy], DirectUpload
unless user.organization?
- can :create, ActsAsVotable::Vote, voter_id: user.id, votable_type: "Debate"
+ can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Debate"
can :vote, Comment
end
if user.level_two_or_three_verified?
can :vote, Proposal, &:published?
- can :create, ActsAsVotable::Vote, voter_id: user.id, votable_type: "Legislation::Proposal"
+ can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Legislation::Proposal"
can :create, Legislation::Answer
diff --git a/config/routes/debate.rb b/config/routes/debate.rb
index 45d6f06f6..03735cc15 100644
--- a/config/routes/debate.rb
+++ b/config/routes/debate.rb
@@ -11,5 +11,5 @@ resources :debates do
put "recommendations/disable", only: :index, controller: "debates", action: :disable_recommendations
end
- resources :votes, controller: "debates/votes", only: :create
+ resources :votes, controller: "debates/votes", only: [:create, :destroy]
end
diff --git a/config/routes/legislation.rb b/config/routes/legislation.rb
index 7e3890c42..9b3f1a0f9 100644
--- a/config/routes/legislation.rb
+++ b/config/routes/legislation.rb
@@ -23,7 +23,7 @@ namespace :legislation do
get :suggest
end
- resources :votes, controller: "proposals/votes", only: :create
+ resources :votes, controller: "proposals/votes", only: [:create, :destroy]
end
resources :draft_versions, only: [:show] do
diff --git a/spec/components/shared/in_favor_against_component_spec.rb b/spec/components/shared/in_favor_against_component_spec.rb
index ece805749..f3ade0ec9 100644
--- a/spec/components/shared/in_favor_against_component_spec.rb
+++ b/spec/components/shared/in_favor_against_component_spec.rb
@@ -22,6 +22,24 @@ describe Shared::InFavorAgainstComponent do
end
end
+ it "can undo vote when the user has already voted" do
+ create(:vote, votable: debate, voter: user)
+
+ sign_in user
+
+ render_inline component
+
+ page.find(".in-favor") do |in_favor_block|
+ expect(in_favor_block).to have_css "form[action*='votes'][method='post']"
+ expect(in_favor_block).to have_css "input[name='_method'][value='delete']", visible: :hidden
+ end
+
+ page.find(".against") do |against_block|
+ expect(against_block).to have_css "form[action*='votes'][method='post']"
+ expect(against_block).not_to have_css "input[name='_method']", visible: :all
+ end
+ end
+
it "does not include result percentages" do
create(:vote, votable: debate)
sign_in(user)
diff --git a/spec/controllers/debates/votes_controller_spec.rb b/spec/controllers/debates/votes_controller_spec.rb
index 9e6090ee6..74bd009d2 100644
--- a/spec/controllers/debates/votes_controller_spec.rb
+++ b/spec/controllers/debates/votes_controller_spec.rb
@@ -40,4 +40,17 @@ describe Debates::VotesController do
end
end
end
+
+ describe "DELETE destroy" do
+ let(:user) { create(:user) }
+ let(:debate) { create(:debate) }
+ let!(:vote) { create(:vote, votable: debate, voter: user) }
+ before { sign_in user }
+
+ it "allows undoing a vote if user is allowed" do
+ expect do
+ delete :destroy, xhr: true, params: { debate_id: debate.id, id: vote }
+ end.to change { debate.reload.votes_for.size }.by(-1)
+ end
+ end
end
diff --git a/spec/controllers/legislation/proposals/votes_controller_spec.rb b/spec/controllers/legislation/proposals/votes_controller_spec.rb
index fd8c780a6..f618277b1 100644
--- a/spec/controllers/legislation/proposals/votes_controller_spec.rb
+++ b/spec/controllers/legislation/proposals/votes_controller_spec.rb
@@ -37,4 +37,20 @@ describe Legislation::Proposals::VotesController do
end.not_to change { proposal.reload.votes_for.size }
end
end
+
+ describe "DELETE destroy" do
+ let(:user) { create(:user, :level_two) }
+ let!(:vote) { create(:vote, votable: proposal, voter: user) }
+ let(:vote_params) do
+ { process_id: legislation_process.id, legislation_proposal_id: proposal.id, id: vote }
+ end
+
+ it "allows undoing a vote" do
+ sign_in user
+
+ expect do
+ delete :destroy, xhr: true, params: vote_params
+ end.to change { proposal.reload.votes_for.size }.by(-1)
+ end
+ end
end
diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb
index 9ba947776..5d13ab111 100644
--- a/spec/models/abilities/common_spec.rb
+++ b/spec/models/abilities/common_spec.rb
@@ -77,6 +77,8 @@ describe Abilities::Common do
it { should be_able_to(:show, debate) }
it { should be_able_to(:create, user.votes.build(votable: debate)) }
it { should_not be_able_to(:create, another_user.votes.build(votable: debate)) }
+ it { should be_able_to(:destroy, user.votes.build(votable: debate)) }
+ it { should_not be_able_to(:destroy, another_user.votes.build(votable: debate)) }
it { should be_able_to(:show, user) }
it { should be_able_to(:edit, user) }
@@ -184,12 +186,15 @@ describe Abilities::Common do
describe "vote legislation proposal" do
context "when user is not level_two_or_three_verified" do
it { should_not be_able_to(:create, user.votes.build(votable: legislation_proposal)) }
+ it { should_not be_able_to(:destroy, user.votes.build(votable: legislation_proposal)) }
end
context "when user is level_two_or_three_verified" do
before { user.update(level_two_verified_at: Date.current) }
it { should be_able_to(:create, user.votes.build(votable: legislation_proposal)) }
it { should_not be_able_to(:create, another_user.votes.build(votable: legislation_proposal)) }
+ it { should be_able_to(:destroy, user.votes.build(votable: legislation_proposal)) }
+ it { should_not be_able_to(:destroy, another_user.votes.build(votable: legislation_proposal)) }
end
end
diff --git a/spec/models/abilities/organization_spec.rb b/spec/models/abilities/organization_spec.rb
index 9bfd9b69f..50d52dda1 100644
--- a/spec/models/abilities/organization_spec.rb
+++ b/spec/models/abilities/organization_spec.rb
@@ -15,6 +15,7 @@ describe "Abilities::Organization" do
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should_not be_able_to(:create, user.votes.build(votable: debate)) }
+ it { should_not be_able_to(:destroy, user.votes.build(votable: debate)) }
it { should be_able_to(:index, Proposal) }
it { should be_able_to(:show, proposal) }
diff --git a/spec/system/votes_spec.rb b/spec/system/votes_spec.rb
index abed25032..96eaf11e5 100644
--- a/spec/system/votes_spec.rb
+++ b/spec/system/votes_spec.rb
@@ -100,20 +100,27 @@ describe "Votes" do
expect(page).to have_content "1 vote"
end
- scenario "Trying to vote multiple times" do
+ scenario "Allow undoing votes" do
visit debate_path(create(:debate))
click_button "I agree"
+
expect(page).to have_content "1 vote"
+ expect(page).not_to have_content "No votes"
+
click_button "I agree"
+
+ expect(page).to have_content "No votes"
expect(page).not_to have_content "2 votes"
within(".in-favor") do
- expect(page).to have_content "100%"
+ expect(page).to have_content "0%"
+ expect(page).to have_css "button[aria-pressed='false']"
end
within(".against") do
expect(page).to have_content "0%"
+ expect(page).to have_css "button[aria-pressed='false']"
end
end
@@ -337,4 +344,31 @@ describe "Votes" do
expect(page).not_to have_button "Support", disabled: :all
end
end
+
+ describe "Legislation Proposals" do
+ let(:proposal) { create(:legislation_proposal) }
+
+ scenario "Allow undoing votes" do
+ login_as verified
+ visit legislation_process_proposal_path(proposal.process, proposal)
+
+ click_button "I agree"
+
+ expect(page).to have_content "1 vote"
+ expect(page).not_to have_content "No votes"
+
+ click_button "I agree"
+
+ expect(page).to have_content "No votes"
+ expect(page).not_to have_content "2 votes"
+
+ within(".in-favor") do
+ expect(page).to have_content "0%"
+ end
+
+ within(".against") do
+ expect(page).to have_content "0%"
+ end
+ end
+ end
end