From bc760a5835f465e029e4be130b5e8f3eb3a3e6fb Mon Sep 17 00:00:00 2001 From: Mahdiar Naufal Date: Sun, 3 Nov 2024 01:36:26 +0100 Subject: [PATCH] Fix upsert all that restore soft-deleted record accidentally --- lib/paranoia.rb | 26 ++++++++++++++++++++++++++ test/paranoia_test.rb | 18 ++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 93bf9730..ecc79d65 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -64,6 +64,21 @@ def paranoia_destroy_attributes def timestamp_attributes_with_current_time timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone } end + + def upsert_all(attributes, returning: nil, unique_by: nil) + return super unless ActiveRecord::VERSION::STRING >= "7.0" + + insert_all = ActiveRecord::InsertAll.new( + self, + attributes, + on_duplicate: :update, + returning: returning, + unique_by: unique_by + ) + insert_all.keys_including_timestamps.delete paranoia_column + insert_all.updatable_columns.delete paranoia_column + insert_all.execute + end end def paranoia_destroy @@ -388,6 +403,17 @@ def deletion_time paranoia_column_value.acts_like?(:time) ? paranoia_column_value : deleted_at end end + + class ActiveRecord::InsertAll + private + if ActiveRecord::VERSION::STRING >= "7.0" + def verify_attributes(attributes) + if keys_including_timestamps != attributes.keys.excluding(model.paranoia_column).to_set + raise ArgumentError, "All objects being inserted must have the same keys" + end + end + end + end end require 'paranoia/rspec' if defined? RSpec diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index e11dc416..7e9e47d2 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -1418,6 +1418,24 @@ def test_update_has_many_through_relation_delete_associations assert_equal 2, employer.jobs.with_deleted.count end + def test_upsert_all_on_soft_deleted_record + e1 = Employer.create(name: "e1") + e2 = Employer.create(name: "e2", deleted_at: Time.current) + assert_nil e1.deleted_at + assert e2.deleted_at != nil + + Employer.upsert_all([ + { id: e1.id, name: "new_e1" }, + { id: e2.id, name: "new_e2" } + ]) + + assert e1.reload.name == "new_e1" + assert e2.reload.name == "new_e2" + + assert_nil e1.reload.deleted_at + assert e2.reload.deleted_at != nil + end + private def get_featureful_model FeaturefulModel.new(:name => "not empty")