Fix #4901: Duplicate disapprovals

* Add uniqueness constraint on post_disapprovals (user_id, post_id).
* Add fix script to remove existing duplicates.
This commit is contained in:
evazion
2021-10-12 20:22:00 -05:00
parent 92e20713e3
commit e72446463e
5 changed files with 60 additions and 1 deletions

View File

@@ -145,6 +145,29 @@ class ApplicationRecord < ActiveRecord::Base
def update!(*args)
all.each { |record| record.update!(*args) }
end
def each_duplicate(*columns)
return enum_for(:each_duplicate, *columns) unless block_given?
group(columns).having("count(*) > 1").count.each do |values, count|
hash = columns.zip(Array.wrap(values)).to_h
yield count: count, **hash
end
end
def destroy_duplicates!(*columns, log: true)
each_duplicate(*columns) do |count:, **columns_with_values|
records = where(columns_with_values).order(:id)
dupes = records.drop(1)
if log
data = { keep: records.first.id, destroy: dupes.map(&:id), count: count, **columns_with_values }
DanbooruLogger.info("Destroying duplicate #{self.name} #{dupes.map(&:id).join(", ")}", data)
end
dupes.each(&:destroy!)
end
end
end
end

View File

@@ -0,0 +1,5 @@
class AddUniqueUserIdAndPostIdIndexToPostDisapprovals < ActiveRecord::Migration[6.1]
def change
add_index :post_disapprovals, [:user_id, :post_id], unique: true
end
end

View File

@@ -4096,6 +4096,13 @@ CREATE INDEX index_post_disapprovals_on_post_id ON public.post_disapprovals USIN
CREATE INDEX index_post_disapprovals_on_user_id ON public.post_disapprovals USING btree (user_id);
--
-- Name: index_post_disapprovals_on_user_id_and_post_id; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX index_post_disapprovals_on_user_id_and_post_id ON public.post_disapprovals USING btree (user_id, post_id);
--
-- Name: index_post_flags_on_creator_id; Type: INDEX; Schema: public; Owner: -
--
@@ -5060,6 +5067,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20210926125826'),
('20211008091234'),
('20211010181657'),
('20211011044400');
('20211011044400'),
('20211013011619');

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env ruby
require_relative "../../config/environment"
PostDisapproval.transaction do
PostDisapproval.destroy_duplicates!(:user_id, :post_id)
end

View File

@@ -45,4 +45,20 @@ class ApplicationRecordTest < ActiveSupport::TestCase
end
end
end
context "ApplicationRecord#destroy_duplicates!" do
should "destroy all duplicates" do
@post1 = create(:post, score: 42)
@post2 = create(:post, score: 42)
@post3 = create(:post, score: 42)
@post4 = create(:post, score: 23)
Post.destroy_duplicates!(:score)
assert_equal(true, Post.exists?(@post1.id))
assert_equal(false, Post.exists?(@post2.id))
assert_equal(false, Post.exists?(@post3.id))
assert_equal(true, Post.exists?(@post4.id))
end
end
end