diff --git a/app/models/concerns/mergeable.rb b/app/models/concerns/mergeable.rb index 1306b25dc8..8609072a39 100644 --- a/app/models/concerns/mergeable.rb +++ b/app/models/concerns/mergeable.rb @@ -75,11 +75,34 @@ def invalid_record(new_record) def create_new_record(attributes) logger.debug "#{self} with id #{attributes["id"]} is new, creating." - increment_metric(:new) record = create(attributes) + increment_metric(:new) record.merge_status = :new record + rescue ActiveRecord::RecordNotUnique + existing_record = with_discarded.find_by(id: attributes["id"]) + raise unless existing_record + + logger.debug "#{self} with id #{attributes["id"]} was created concurrently, merging instead." + merge_concurrent_record(existing_record, attributes) + end + + def merge_concurrent_record(existing_record, attributes) + new_record = new(attributes) + + case merge_status(new_record, existing_record) + when :discarded + discarded_record(existing_record) + when :updated + update_existing_record(existing_record, attributes) + when :identical + return_identical_record(existing_record) + when :old + return_old_record(existing_record) + else + raise ActiveRecord::RecordNotUnique + end end def update_existing_record(existing_record, attributes) diff --git a/spec/models/concerns/mergeable_spec.rb b/spec/models/concerns/mergeable_spec.rb index 0bc9d6a561..a472a3ec35 100644 --- a/spec/models/concerns/mergeable_spec.rb +++ b/spec/models/concerns/mergeable_spec.rb @@ -83,6 +83,35 @@ Patient.merge(updated_patient.attributes) end + it "merges an existing record when create races with a concurrent insert" do + facility = create(:facility) + patient = create(:patient, registration_facility: facility) + encountered_on = Date.new(2019, 1, 1) + encounter_id = Encounter.generate_id(facility.id, patient.id, encountered_on) + existing_encounter = create(:encounter, + id: encounter_id, + facility: facility, + patient: patient, + encountered_on: encountered_on) + newer_attributes = existing_encounter.attributes.merge( + "device_updated_at" => existing_encounter.device_updated_at + 1.hour + ) + + allow(Encounter).to receive(:create).and_raise( + ActiveRecord::RecordNotUnique.new( + 'duplicate key value violates unique constraint "encounters_pkey"' + ) + ) + + result = Encounter.merge(newer_attributes) + + expect(result.id).to eq(encounter_id) + expect(result.merge_status).to eq(:updated) + expect(Encounter.count).to eq(1) + expect(existing_encounter.reload.device_updated_at.to_i) + .to eq(newer_attributes["device_updated_at"].to_i) + end + it "works for all models" do new_address = FactoryBot.build(:address) Address.merge(new_address.attributes)