AfterDelete.Dispatcher
Collects the deleted records its predicate accepts and hands them over once per chunk. Enqueue a job, send one email or publish events.
Interface
apex
public class AfterDelete {
public interface Dispatcher extends Handler {
Boolean dispatchOnAfterDeleteWhen(TriggerHandler.DeleteRecord record);
void dispatchOnAfterDelete(TriggerHandler.DeleteRecords records);
}
}dispatchOnAfterDeleteWhen(record): called once per record in the chunk; it only selects records. Returntrueto include the record in the dispatch.dispatchOnAfterDelete(records): called once, after every record was checked, with the qualified records; not called when none qualified.
Example
apex
public with sharing class ContactDispatcher implements AfterDelete.Dispatcher {
public Boolean dispatchOnAfterDeleteWhen(TriggerHandler.DeleteRecord record) {
return record.isNotNull(Contact.Email);
}
public void dispatchOnAfterDelete(TriggerHandler.DeleteRecords records) {
System.enqueueJob(new ContactSyncJob(records.getIdsOf(Contact.AccountId)));
}
public class ContactSyncJob implements Queueable {
private Set<Id> recordIds;
public ContactSyncJob(Set<Id> recordIds) {
this.recordIds = recordIds;
}
public void execute(QueueableContext context) {
}
}
}apex
public with sharing class ContactRemovalSyncDispatcher implements AfterDelete.Dispatcher {
public Boolean dispatchOnAfterDeleteWhen(TriggerHandler.DeleteRecord record) {
return record.isNotBlank(Contact.Email) && record.isNull(Contact.MasterRecordId);
}
public void dispatchOnAfterDelete(TriggerHandler.DeleteRecords records) {
System.enqueueJob(new RemovalSyncJob(records.getValuesOf(Contact.Email)));
}
public class RemovalSyncJob implements Queueable, Database.AllowsCallouts {
private Set<String> emails;
public RemovalSyncJob(Set<String> emails) {
this.emails = emails;
}
public void execute(QueueableContext context) {
}
}
}Good to Know
- Pass values, not the deleted Ids. The job runs after the commit, when the deleted rows can no longer be queried. Pass the values it needs, or the Ids of records that still exist.
- No synchronous callouts. A callout from a trigger throws a
System.CalloutException. Call out from a Queueable that implementsDatabase.AllowsCallouts. - No unit of work. DML here runs at once and is not merged with the Writers' unit. Use a Writer for record writes.
- Merge losers arrive here too. Skip them with
record.isNull(Contact.MasterRecordId)when a merge should not count as a removal. - Writer wins. A class that also implements
AfterDelete.Writerruns only as a Writer.
Test
apex
@IsTest
static void dispatchOnAfterDeleteEnqueuesOneJob() {
// Setup
Contact oldContact = new Contact(LastName = 'Doe', Email = 'jane@acme.com');
TriggerHandler.DeleteRecords records = new TriggerHandler.DeleteTriggerRecords(new List<TriggerHandler.TriggerRecord>{ new TriggerHandler.TriggerRecord(null, oldContact) });
// Test
new ContactDispatcher().dispatchOnAfterDelete(records);
// Verify
Assert.areEqual(1, Limits.getQueueableJobs(), 'One job should be enqueued for the chunk.');
}