FEATURE: rake task for merging users

This commit is contained in:
Gerhard Schlager
2018-02-28 22:21:52 +01:00
parent fffd1a6602
commit 7a2183e8ab
6 changed files with 1632 additions and 10 deletions

View File

@ -0,0 +1,59 @@
module DiscoursePoll
class VotesUpdater
def self.merge_users!(source_user, target_user)
post_ids = PostCustomField.where(name: DiscoursePoll::VOTES_CUSTOM_FIELD)
.where("value :: JSON -> ? IS NOT NULL", source_user.id.to_s)
.pluck(:post_id)
post_ids.each do |post_id|
DistributedMutex.synchronize("#{DiscoursePoll::MUTEX_PREFIX}-#{post_id}") do
post = Post.find_by(id: post_id)
update_votes(post, source_user, target_user) if post
end
end
end
def self.update_votes(post, source_user, target_user)
polls = post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
votes = post.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]
return if polls.nil? || votes.nil? || !votes.has_key?(source_user.id.to_s)
if votes.has_key?(target_user.id.to_s)
remove_votes(polls, votes, source_user)
else
replace_voter_id(polls, votes, source_user, target_user)
end
post.save_custom_fields(true)
end
def self.remove_votes(polls, votes, source_user)
votes.delete(source_user.id.to_s).each do |poll_name, option_ids|
poll = polls[poll_name]
next unless poll && option_ids
poll["options"].each do |option|
if option_ids.include?(option["id"])
option["votes"] -= 1
voter_ids = option["voter_ids"]
voter_ids.delete(source_user.id) if voter_ids
end
end
end
end
def self.replace_voter_id(polls, votes, source_user, target_user)
votes[target_user.id.to_s] = votes.delete(source_user.id.to_s)
polls.each_value do |poll|
next unless poll["public"] == "true"
poll["options"].each do |option|
voter_ids = option["voter_ids"]
voter_ids << target_user.id if voter_ids&.delete(source_user.id)
end
end
end
end
end

View File

@ -18,10 +18,12 @@ after_initialize do
DEFAULT_POLL_NAME ||= "poll".freeze
POLLS_CUSTOM_FIELD ||= "polls".freeze
VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
MUTEX_PREFIX ||= PLUGIN_NAME
autoload :PostValidator, "#{Rails.root}/plugins/poll/lib/post_validator"
autoload :PollsValidator, "#{Rails.root}/plugins/poll/lib/polls_validator"
autoload :PollsUpdater, "#{Rails.root}/plugins/poll/lib/polls_updater"
autoload :VotesUpdater, "#{Rails.root}/plugins/poll/lib/votes_updater"
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
@ -385,6 +387,10 @@ after_initialize do
polls: post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD])
end
on(:merging_users) do |source_user, target_user|
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
end
add_to_serializer(:post, :polls, false) do
polls = post_custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].dup

View File

@ -0,0 +1,94 @@
require 'rails_helper'
describe DiscoursePoll::VotesUpdater do
let(:target_user) { Fabricate(:user_single_email, username: 'alice', email: 'alice@example.com') }
let(:source_user) { Fabricate(:user_single_email, username: 'alice1', email: 'alice@work.com') }
let(:walter) { Fabricate(:walter_white) }
let(:target_user_id) { target_user.id.to_s }
let(:source_user_id) { source_user.id.to_s }
let(:walter_id) { walter.id.to_s }
let(:post_with_two_polls) do
raw = <<~RAW
[poll type=multiple min=2 max=3 public=true]
- Option 1
- Option 2
- Option 3
[/poll]
[poll name=private_poll]
- Option 1
- Option 2
- Option 3
[/poll]
RAW
Fabricate(:post, raw: raw)
end
let(:option1_id) { "63eb791ab5d08fc4cc855a0703ac0dd1" }
let(:option2_id) { "773a193533027393806fff6edd6c04f7" }
let(:option3_id) { "f42f567ca3136ee1322d71d7745084c7" }
def vote(post, user, option_ids, poll_name = nil)
poll_name ||= DiscoursePoll::DEFAULT_POLL_NAME
DiscoursePoll::Poll.vote(post.id, poll_name, option_ids, user)
end
it "should move votes to the target_user when only the source_user voted" do
vote(post_with_two_polls, source_user, [option1_id, option3_id])
vote(post_with_two_polls, walter, [option1_id, option2_id])
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
post_with_two_polls.reload
polls = post_with_two_polls.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
expect(polls["poll"]["options"][0]["votes"]).to eq(2)
expect(polls["poll"]["options"][1]["votes"]).to eq(1)
expect(polls["poll"]["options"][2]["votes"]).to eq(1)
expect(polls["poll"]["options"][0]["voter_ids"]).to contain_exactly(target_user.id, walter.id)
expect(polls["poll"]["options"][1]["voter_ids"]).to contain_exactly(walter.id)
expect(polls["poll"]["options"][2]["voter_ids"]).to contain_exactly(target_user.id)
votes = post_with_two_polls.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]
expect(votes.keys).to contain_exactly(target_user_id, walter_id)
expect(votes[target_user_id]["poll"]).to contain_exactly(option1_id, option3_id)
expect(votes[walter_id]["poll"]).to contain_exactly(option1_id, option2_id)
end
it "should delete votes of the source_user if the target_user voted" do
vote(post_with_two_polls, source_user, [option1_id, option3_id])
vote(post_with_two_polls, target_user, [option2_id, option3_id])
vote(post_with_two_polls, walter, [option1_id, option2_id])
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
post_with_two_polls.reload
polls = post_with_two_polls.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
expect(polls["poll"]["options"][0]["votes"]).to eq(1)
expect(polls["poll"]["options"][1]["votes"]).to eq(2)
expect(polls["poll"]["options"][2]["votes"]).to eq(1)
expect(polls["poll"]["options"][0]["voter_ids"]).to contain_exactly(walter.id)
expect(polls["poll"]["options"][1]["voter_ids"]).to contain_exactly(target_user.id, walter.id)
expect(polls["poll"]["options"][2]["voter_ids"]).to contain_exactly(target_user.id)
votes = post_with_two_polls.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]
expect(votes.keys).to contain_exactly(target_user_id, walter_id)
expect(votes[target_user_id]["poll"]).to contain_exactly(option2_id, option3_id)
expect(votes[walter_id]["poll"]).to contain_exactly(option1_id, option2_id)
end
it "does not add voter_ids unless the poll is public" do
vote(post_with_two_polls, source_user, [option1_id, option3_id], "private_poll")
vote(post_with_two_polls, walter, [option1_id, option2_id], "private_poll")
DiscoursePoll::VotesUpdater.merge_users!(source_user, target_user)
post_with_two_polls.reload
polls = post_with_two_polls.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
polls["private_poll"]["options"].each { |o| expect(o).to_not have_key("voter_ids") }
end
end