mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-25 20:15:50 +08:00
What is the problem? The poll plugin does not handle the `post_moved` event fired by `PostMover`. For normal reply moves `Post#id` is unchanged so poll data is retained, but when moving the first post of a topic (`PostMover` recreates it via `PostCreator#create!` in the destination topic) or when an admin moves a reply with the "keep a copy in the original topic" option (`PostMover#move` with `freeze_original: true`, which duplicates the post), the new post gets a different `Post#id` and the `Poll` records remain pointing at the old `Post#id` via `Poll#post_id`, orphaning them along with their associated `PollOption` and `PollVote` records. What is the solution? Add an `on(:post_moved)` handler in `plugins/poll/plugin.rb` that: 1. Skips processing when `Post#id` is unchanged (normal reply moves). 2. Removes empty `Poll` and `PollOption` records that the poll plugin's `validate_post` hook auto-creates when `PostCreator` rebuilds the post from raw. Deletes in FK order: `PollVote` → `PollOption` → `Poll`. 3. Reassigns the original polls — which carry the actual votes — to the new post by updating `Poll#post_id` via `Poll.update_all`. 4. Updates `DiscoursePoll::HAS_POLLS` custom field on both posts via `PollsUpdater.update_post_custom_fields` — sets it on the new post (needed for the `freeze_original` path which bypasses `PostCreator`) and clears it on the old post so `TopicView#polls` does not issue unnecessary queries. 5. Wraps steps 2-3 in an `ActiveRecord::Base.transaction`.
97 lines
3.2 KiB
Ruby
Vendored
97 lines
3.2 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe "Poll post_moved handler" do
|
|
fab!(:admin)
|
|
fab!(:user1, :user)
|
|
fab!(:user2, :user)
|
|
|
|
fab!(:first_post) do
|
|
Fabricate(
|
|
:post,
|
|
user: admin,
|
|
raw: "[poll name=first_post_poll type=regular]\n- Option A\n- Option B\n[/poll]",
|
|
)
|
|
end
|
|
|
|
fab!(:source_topic) { first_post.topic }
|
|
|
|
fab!(:reply) do
|
|
Fabricate(
|
|
:post,
|
|
topic: source_topic,
|
|
user: admin,
|
|
raw: "[poll name=reply_poll type=regular]\n- Option A\n- Option B\n[/poll]",
|
|
)
|
|
end
|
|
|
|
fab!(:first_post_poll) { first_post.polls.find_by(name: "first_post_poll") }
|
|
fab!(:reply_poll) { reply.polls.find_by(name: "reply_poll") }
|
|
|
|
fab!(:user1_first_post_poll_votes) do
|
|
first_post_poll.poll_options.map do |option|
|
|
Fabricate(:poll_vote, poll: first_post_poll, poll_option: option, user: user1)
|
|
end
|
|
end
|
|
|
|
fab!(:user2_reply_poll_votes) do
|
|
reply_poll.poll_options.map do |option|
|
|
Fabricate(:poll_vote, poll: reply_poll, poll_option: option, user: user2)
|
|
end
|
|
end
|
|
|
|
fab!(:destination_topic) { Fabricate(:post, user: admin).topic }
|
|
|
|
before { Jobs.run_immediately! }
|
|
|
|
it "moves polls, options, and votes when the first post is moved" do
|
|
original_options = first_post_poll.poll_options.to_a
|
|
|
|
source_topic.move_posts(admin, [first_post.id], destination_topic_id: destination_topic.id)
|
|
|
|
new_post = destination_topic.posts.last
|
|
moved_poll = new_post.polls.find_by(name: "first_post_poll")
|
|
|
|
expect(moved_poll).to eq(first_post_poll)
|
|
expect(moved_poll.poll_options).to contain_exactly(*original_options)
|
|
expect(moved_poll.poll_votes.map { |vote| [vote.user, vote.poll_option] }).to contain_exactly(
|
|
*original_options.map { |option| [user1, option] },
|
|
)
|
|
|
|
expect(new_post.custom_fields[DiscoursePoll::HAS_POLLS]).to eq(true)
|
|
expect(Poll.exists?(post_id: first_post.id)).to eq(false)
|
|
end
|
|
|
|
it "moves polls when a copy is kept in the original topic" do
|
|
original_options = reply_poll.poll_options.to_a
|
|
|
|
PostMover.new(source_topic, admin, [reply.id], options: { freeze_original: true }).to_topic(
|
|
destination_topic.id,
|
|
)
|
|
|
|
moved_reply = destination_topic.posts.last
|
|
moved_poll = moved_reply.polls.find_by(name: "reply_poll")
|
|
|
|
expect(moved_poll).to eq(reply_poll)
|
|
expect(moved_poll.poll_options).to contain_exactly(*original_options)
|
|
expect(moved_poll.poll_votes.map { |vote| [vote.user, vote.poll_option] }).to contain_exactly(
|
|
*original_options.map { |option| [user2, option] },
|
|
)
|
|
|
|
expect(moved_reply.custom_fields[DiscoursePoll::HAS_POLLS]).to eq(true)
|
|
expect(Poll.exists?(post_id: reply.id)).to eq(false)
|
|
expect(reply.reload.custom_fields[DiscoursePoll::HAS_POLLS]).to eq(nil)
|
|
end
|
|
|
|
it "preserves polls when a reply is moved without creating a new post" do
|
|
source_topic.move_posts(admin, [reply.id], destination_topic_id: destination_topic.id)
|
|
|
|
expect(reply.reload.topic_id).to eq(destination_topic.id)
|
|
|
|
poll = reply.polls.find_by(name: "reply_poll")
|
|
expect(poll).to eq(reply_poll)
|
|
expect(poll.poll_options).to contain_exactly(*reply_poll.poll_options)
|
|
expect(poll.poll_votes.map { |vote| [vote.user, vote.poll_option] }).to contain_exactly(
|
|
*poll.poll_options.map { |option| [user2, option] },
|
|
)
|
|
end
|
|
end
|