Skip to content

BeforeDelete.Writer ​

Change other records or publish platform events before the delete, through a unit of work.

Signature

apex
public interface Writer extends BeforeDelete.Handler {
    Boolean writeOnBeforeDeleteWhen(TriggerTypes.DeleteRecord record);
    void writeOnBeforeDelete(TriggerTypes.DeleteRecord record, TriggerTypes.UnitOfWork unitOfWork);
}
  • writeOnBeforeDeleteWhen(record): called once per record in the chunk. Return true to write for the record.
  • writeOnBeforeDelete(record, unitOfWork): called right after its predicate returns true, with this Writer’s unit of work.

Example

apex
public with sharing class ContactWriter implements BeforeDelete.Writer {
    public Boolean writeOnBeforeDeleteWhen(TriggerTypes.DeleteRecord record) {
        return record.isNotNull(Contact.AccountId);
    }

    public void writeOnBeforeDelete(TriggerTypes.DeleteRecord record, TriggerTypes.UnitOfWork unitOfWork) {
        unitOfWork.toInsert(new Task(WhatId = ((Contact) record.getOldSObject()).AccountId, Subject = 'Review contact'));
    }
}
apex
public with sharing class ContactReportsReassignWriter implements BeforeDelete.Writer, BeforeDelete.RelatedQuery {
    public Map<String, BeforeDelete.RecordsProvider> queryRelatedOnBeforeDelete() {
        return new Map<String, BeforeDelete.RecordsProvider>{ 'directReports' => new DirectReportsProvider() };
    }

    public Boolean writeOnBeforeDeleteWhen(TriggerTypes.DeleteRecord record) {
        return !record.getRelated('directReports').getAllWhereKeyEquals(record.getId()).isEmpty();
    }

    public void writeOnBeforeDelete(TriggerTypes.DeleteRecord record, TriggerTypes.UnitOfWork unitOfWork) {
        Id managerId = ((Contact) record.getOldSObject()).ReportsToId;

        for (SObject report : record.getRelated('directReports').getAllWhereKeyEquals(record.getId())) {
            unitOfWork.toUpdate(new Contact(Id = report.Id, ReportsToId = managerId));
        }
    }

    private without sharing class DirectReportsProvider implements BeforeDelete.RecordsProvider {
        public List<SObject> query(TriggerTypes.DeleteRecords records) {
            return [SELECT Id, ReportsToId FROM Contact WHERE ReportsToId IN :records.getIds() AND Id NOT IN :records.getIds()];
        }

        public String keyOf(SObject row) {
            return ((Contact) row).ReportsToId;
        }
    }
}

Rules ​

  • Register, do not run DML. toInsert, toUpdate, toUpsert, toDelete and toPublish (platform events) take one record each.
  • Commits before the delete. The shared unit of work commits once, after the last handler of the run, while the records still exist. A Writer with ContinueOnError gets an automatic unit, committed right after it. OwnUnitOfWork supplies your own.
  • Links still point at the rows. Records that look up to the deleted rows still hold the Ids here, so a query by records.getIds() finds them.
  • Validator wins. A class that also implements BeforeDelete.Validator runs only as a Validator.

WARNING

Leave the records being deleted alone. A toDelete of one fails the commit with SELF_REFERENCE_FROM_TRIGGER, and an update of one is lost when the delete goes through.

Test ​

apex
@IsTest
static void writeOnBeforeDeleteWhenQualifiesContactWithAccount() {
    // Setup
    Contact oldContact = new Contact(AccountId = new TriggerTypes.RandomIdGenerator().get(Account.SObjectType));

    // Test
    Boolean qualifies = new ContactWriter().writeOnBeforeDeleteWhen(new TriggerTypes.TriggerRecord(null, oldContact));

    // Verify
    Assert.isTrue(qualifies, 'A contact with an account should qualify.');
}