Remove reactions to a user when deleting the user (#15724)

* Remove reactions to a user when deleting the user

Fixes #15245

---

I have no idea why `create(:vomit_reaction, reactable: user)` gave a
validation error about category being invalid, but `build().save`
worked fine.

* Move relation details to reaction scope `for_user`

Add a user method to access this by passing self to reaction scope

* Create a moderator when flagging user in factory

This might be a missing requirement in the reactions factory - but
this suppresses an error about invalid category I was getting when
setting a privileged reaction on the user via

    create(:vomit_reaction, reactable: user)

Everywhere else it looks like we do this with `user: moderator` (where
there's a trusted user in the surrounding context).

* Move trusted user requirement into vomit_reaction factory

* Add a data update script to remove reactions to deleted users

This cleans up the remaining invalid references and should make the
change in #15249 obsolete (i.e. we don't need to limit the view to
exclude items that no longer exist).
This commit is contained in:
Daniel Uber 2021-12-10 11:24:23 -06:00 committed by GitHub
parent 428dd1bd2c
commit 421dad2900
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 45 additions and 1 deletions

View file

@ -42,6 +42,7 @@ class Reaction < ApplicationRecord
.or(user_vomits.where(user_id: user.id))
}
scope :privileged_category, -> { where(category: PRIVILEGED_CATEGORIES) }
scope :for_user, ->(user) { where(reactable: user) }
validates :category, inclusion: { in: CATEGORIES }
validates :reactable_type, inclusion: { in: REACTABLE_TYPES }

View file

@ -536,6 +536,10 @@ class User < ApplicationRecord
notification_setting.email_follower_notifications
end
def reactions_to
Reaction.for_user(self)
end
protected
# Send emails asynchronously

View file

@ -37,6 +37,7 @@ module Users
def delete_profile_info(user)
user.notifications.delete_all
user.reactions.delete_all
user.reactions_to.delete_all
user.follows.delete_all
Follow.followable_user(user.id).delete_all
user.mentions.delete_all

View file

@ -0,0 +1,11 @@
module DataUpdateScripts
class RemoveDanglingUserReactions
def run
# reactions to users who have been deleted should be removed:
Reaction
.where(reactable_type: "User")
.where.not(reactable_id: User.all.select(:id))
.delete_all
end
end
end

View file

@ -22,7 +22,7 @@ FactoryBot.define do
end
factory :vomit_reaction, class: "Reaction" do
user
user { create(:user, :trusted) }
association :reactable, factory: :article
category { "vomit" }

View file

@ -0,0 +1,19 @@
require "rails_helper"
require Rails.root.join(
"lib/data_update_scripts/20211209213849_remove_dangling_user_reactions.rb",
)
describe DataUpdateScripts::RemoveDanglingUserReactions do
let(:user_to_delete) { create(:user) }
before do
create(:vomit_reaction, reactable: user_to_delete)
# there's no dependent callback on reactions_to so the reaction is not removed
user_to_delete.destroy
end
it "removes reactions to deleted users" do
expect { described_class.new.run }.to change(Reaction, :count).by(-1)
end
end

View file

@ -68,6 +68,14 @@ RSpec.describe Users::Delete, type: :service do
end.to change(FieldTest::Membership, :count).by(-1)
end
it "deletes reactions to the user" do
create(:vomit_reaction, reactable: user)
expect do
described_class.call(user)
end.to change(Reaction, :count).by(-1)
end
# check that all the associated records are being destroyed,
# except for those that are kept explicitly (kept_associations)
describe "deleting associations" do