FIX: Upsert topic votes count atomically (#71)
The `Topic#update_vote_count` method should be concurrency-safe to prevent unique constraint violation errors when multiple processes try to insert a topic_votes_count record for the same topic simultaneously. Signed-off-by: OsamaSayegh <asooomaasoooma90@gmail.com>
This commit is contained in:
parent
148d76facc
commit
8dbc75f965
|
@ -38,3 +38,17 @@ module DiscourseVoting
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: discourse_voting_category_settings
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# category_id :integer
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_discourse_voting_category_settings_on_category_id (category_id) UNIQUE
|
||||
#
|
||||
|
|
|
@ -7,3 +7,18 @@ module DiscourseVoting
|
|||
belongs_to :topic
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: discourse_voting_topic_vote_count
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# topic_id :integer
|
||||
# votes_count :integer
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_discourse_voting_topic_vote_count_on_topic_id (topic_id) UNIQUE
|
||||
#
|
||||
|
|
|
@ -8,3 +8,19 @@ module DiscourseVoting
|
|||
belongs_to :topic
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: discourse_voting_votes
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# topic_id :integer
|
||||
# user_id :integer
|
||||
# archive :boolean default(FALSE)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_discourse_voting_votes_on_user_id_and_topic_id (user_id,topic_id) UNIQUE
|
||||
#
|
||||
|
|
|
@ -27,8 +27,16 @@ module DiscourseVoting
|
|||
def update_vote_count
|
||||
count = self.votes.count
|
||||
|
||||
topic_vote_count = self.topic_vote_count || DiscourseVoting::TopicVoteCount.new(topic: self)
|
||||
topic_vote_count.update!(votes_count: count)
|
||||
DB.exec(<<~SQL, topic_id: self.id, votes_count: count)
|
||||
INSERT INTO discourse_voting_topic_vote_count
|
||||
(topic_id, votes_count, created_at, updated_at)
|
||||
VALUES
|
||||
(:topic_id, :votes_count, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (topic_id) DO UPDATE SET
|
||||
votes_count = :votes_count,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE discourse_voting_topic_vote_count.topic_id = :topic_id
|
||||
SQL
|
||||
end
|
||||
|
||||
def who_voted
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe DiscourseVoting::TopicExtension do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:user2) { Fabricate(:user) }
|
||||
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let(:topic2) { Fabricate(:topic) }
|
||||
|
||||
before do
|
||||
SiteSetting.voting_enabled = true
|
||||
SiteSetting.voting_show_who_voted = true
|
||||
end
|
||||
|
||||
describe '#update_vote_count' do
|
||||
it 'upserts topic votes count' do
|
||||
topic.update_vote_count
|
||||
topic2.update_vote_count
|
||||
|
||||
expect(topic.reload.topic_vote_count.votes_count).to eq(0)
|
||||
expect(topic2.reload.topic_vote_count.votes_count).to eq(0)
|
||||
|
||||
DiscourseVoting::Vote.create!(user: user, topic: topic)
|
||||
topic.update_vote_count
|
||||
topic2.update_vote_count
|
||||
|
||||
expect(topic.reload.topic_vote_count.votes_count).to eq(1)
|
||||
expect(topic2.reload.topic_vote_count.votes_count).to eq(0)
|
||||
|
||||
DiscourseVoting::Vote.create!(user: user2, topic: topic)
|
||||
DiscourseVoting::Vote.create!(user: user, topic: topic2)
|
||||
topic.update_vote_count
|
||||
topic2.update_vote_count
|
||||
|
||||
expect(topic.reload.topic_vote_count.votes_count).to eq(2)
|
||||
expect(topic2.reload.topic_vote_count.votes_count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue