diff --git a/plugins/poll/db/migrate/20230614041219_delete_duplicate_poll_votes.rb b/plugins/poll/db/migrate/20230614041219_delete_duplicate_poll_votes.rb new file mode 100644 index 00000000000..4b1fb71a195 --- /dev/null +++ b/plugins/poll/db/migrate/20230614041219_delete_duplicate_poll_votes.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class DeleteDuplicatePollVotes < ActiveRecord::Migration[7.0] + def up + execute <<~SQL + DELETE FROM poll_votes + WHERE (poll_id, user_id, updated_at) NOT IN ( + SELECT poll_id, user_id, MAX(updated_at) + FROM poll_votes + GROUP BY poll_id, user_id + ) + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb index be2cb91810c..9efaa93f2ef 100644 --- a/plugins/poll/plugin.rb +++ b/plugins/poll/plugin.rb @@ -169,7 +169,22 @@ after_initialize do end on(:merging_users) do |source_user, target_user| - PollVote.where(user_id: source_user.id).update_all(user_id: target_user.id) + DB.exec(<<-SQL, source_user_id: source_user.id, target_user_id: target_user.id) + DELETE FROM poll_votes + WHERE poll_id IN ( + SELECT poll_id + FROM poll_votes + WHERE user_id = :source_user_id + INTERSECT + SELECT poll_id + FROM poll_votes + WHERE user_id = :target_user_id + ) AND user_id = :source_user_id; + + UPDATE poll_votes + SET user_id = :target_user_id + WHERE user_id = :source_user_id; + SQL end add_to_class(:topic_view, :polls) do diff --git a/plugins/poll/spec/fabricators/poll_fabricator.rb b/plugins/poll/spec/fabricators/poll_fabricator.rb new file mode 100644 index 00000000000..33049dfada0 --- /dev/null +++ b/plugins/poll/spec/fabricators/poll_fabricator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +Fabricator(:poll) do + post + name { sequence(:name) { |i| "Poll #{i}" } } +end + +Fabricator(:poll_option) do + poll + html { sequence(:html) { |i| "Poll Option #{i}" } } + digest { sequence(:digest) { |i| "#{i}" } } +end + +Fabricator(:poll_vote) do + poll + poll_option + user +end diff --git a/plugins/poll/spec/integration/user_merger_spec.rb b/plugins/poll/spec/integration/user_merger_spec.rb new file mode 100644 index 00000000000..8c5f14f9ffd --- /dev/null +++ b/plugins/poll/spec/integration/user_merger_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.describe UserMerger do + fab!(:target_user) { Fabricate(:user, username: "galahad", email: "galahad@knights.com") } + fab!(:source_user) { Fabricate(:user, username: "lancelot", email: "lancelot@knights.com") } + fab!(:poll) { Fabricate(:poll) } + + it "deletes source_user vote if target_user already voted" do + Fabricate(:poll_vote, poll: poll, user: target_user) + Fabricate(:poll_vote, poll: poll, user: source_user) + + DiscourseEvent.trigger(:merging_users, source_user, target_user) + + expect(PollVote.where(user: source_user).count).to eq(0) + end + + it "keeps only target_user vote if duplicate" do + target_vote = Fabricate(:poll_vote, poll: poll, user: target_user) + Fabricate(:poll_vote, poll: poll, user: source_user) + + DiscourseEvent.trigger(:merging_users, source_user, target_user) + + expect(PollVote.where(user: target_user).to_json).to eq([target_vote].to_json) + end + + it "reassigns source_user vote to target_user if not duplicate" do + Fabricate(:poll_vote, poll: poll, user: source_user) + + expect { DiscourseEvent.trigger(:merging_users, source_user, target_user) }.to change( + PollVote.where(user: target_user), + :count, + ).from(0).to(1) + end + + it "keeps any existing target_user votes" do + Fabricate(:poll_vote, poll: poll, user: target_user) + + expect { DiscourseEvent.trigger(:merging_users, source_user, target_user) }.to not_change( + PollVote.where(user: target_user), + :count, + ) + end +end