AfterInsert.Writer
Creates, updates or deletes other records, or publishes platform events, after new records are saved. Register the writes on the unit of work: they commit with the save, without DML in your loop.
Interface
apex
public class AfterInsert {
public interface Writer extends Handler {
Boolean writeOnAfterInsertWhen(TriggerHandler.InsertRecord record);
void writeOnAfterInsert(TriggerHandler.InsertRecord record, TriggerHandler.UnitOfWork unitOfWork);
}
}writeOnAfterInsertWhen(record): called once per record in the chunk. Returntrueto write for the record.writeOnAfterInsert(record, unitOfWork): called right after its predicate returns true, with this Writer’s unit of work.
Example
apex
public with sharing class ContactWriter implements AfterInsert.Writer {
public Boolean writeOnAfterInsertWhen(TriggerHandler.InsertRecord record) {
return record.isNotNull(Contact.AccountId);
}
public void writeOnAfterInsert(TriggerHandler.InsertRecord record, TriggerHandler.UnitOfWork unitOfWork) {
unitOfWork.toInsert(new Task(WhatId = ((Contact) record.getNewSObject()).AccountId, Subject = 'Review contact'));
}
}cls
public with sharing class AccountWelcomeTaskWriter implements AfterInsert.Writer, AfterInsert.ParentQuery, AfterInsert.ContinueOnError {
public Map<SObjectField, TriggerHandler.ParentFields> queryParentsOnAfterInsert() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Account.OwnerId => TriggerHandler.ParentFields.with(User.Name, User.Email) };
}
public Boolean writeOnAfterInsertWhen(TriggerHandler.InsertRecord record) {
return record.startsWith(Account.Type, 'Customer');
}
public void writeOnAfterInsert(TriggerHandler.InsertRecord record, TriggerHandler.UnitOfWork unitOfWork) {
Account accountRecord = (Account) record.getNewSObject();
User owner = (User) record.getNewParent('Owner');
unitOfWork.toInsert(
new Task(
WhatId = record.getId(),
OwnerId = accountRecord.OwnerId,
Subject = 'Onboarding call - ' + accountRecord.Name,
Description = 'New customer assigned to ' + owner?.Name + ' (' + owner?.Email + '). Confirm the billing address and the primary contact.',
ActivityDate = Date.today().addDays(3),
Priority = 'High',
Status = 'Not Started'
)
);
}
}cls
public with sharing class ContactOwnerAlignmentWriter implements AfterInsert.Writer, AfterInsert.ParentQuery, AfterInsert.Bypassable {
public static Boolean isDisabled = false;
public Map<SObjectField, TriggerHandler.ParentFields> queryParentsOnAfterInsert() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Contact.AccountId => TriggerHandler.ParentFields.with(Account.OwnerId).with('Owner', User.IsActive) };
}
public Boolean bypassOnAfterInsertWhen() {
return ContactOwnerAlignmentWriter.isDisabled;
}
public Boolean writeOnAfterInsertWhen(TriggerHandler.InsertRecord record) {
Contact contactRecord = (Contact) record.getNewSObject();
Account accountRecord = (Account) record.getNewParent('Account');
return accountRecord?.Owner?.IsActive == true && accountRecord.OwnerId != contactRecord.OwnerId;
}
public void writeOnAfterInsert(TriggerHandler.InsertRecord record, TriggerHandler.UnitOfWork unitOfWork) {
Account accountRecord = (Account) record.getNewParent('Account');
unitOfWork.toUpdate(new Contact(Id = record.getId(), OwnerId = accountRecord.OwnerId));
}
}Good to Know
- Register, don't run DML. By default, Writers share one unit of work that merges their writes and commits once, after the last handler. A direct
insertorupdateruns at once and skips the unit. - Later handlers don't see your writes. Registered records are not in the database until the unit commits. A later Writer reads the rows as they were saved.
- The row is read-only.
record.put(…)throws, and the insert fails. To change the new record, registertoUpdate(new Contact(Id = record.getId(), …)). That saves it again and runs the update triggers. - Inserting the same object runs after insert again. Insert contexts have no RecursionGuard. Only your predicate stops the chain.
- Writer wins. A class that also implements
AfterInsert.Dispatcherruns only as a Writer.
Test
apex
@IsTest
static void writeOnAfterInsertRegistersOnboardingTask() {
// Setup
Id accountId = new TriggerHandler.RandomIdGenerator().get(Account.SObjectType);
TriggerHandler.InsertRecord record = new TriggerHandler.TriggerRecord(new Account(Id = accountId, Name = 'Acme', Type = 'Customer - Direct'), null);
RecordingUnitOfWork unitOfWork = new RecordingUnitOfWork();
// Test
new AccountWelcomeTaskWriter().writeOnAfterInsert(record, unitOfWork);
// Verify
Assert.areEqual('Onboarding call - Acme', ((Task) unitOfWork.inserted[0]).Subject, 'The task subject should name the account.');
}RecordingUnitOfWork is a small test class that implements TriggerHandler.UnitOfWork and keeps what it receives. See Testing.
