BeforeUpdate.PriorParentQuery
Reads fields of the parent a lookup pointed to before this update. Use it to compare the old and the new parent, or to name the previous one.
Interface
apex
public class BeforeUpdate {
public interface PriorParentQuery {
Map<SObjectField, TriggerHandler.ParentFields> queryPriorParentsOnBeforeUpdate();
}
}queryPriorParentsOnBeforeUpdate(): called once per chunk, before the first handler runs; not called for a bypassed handler. Returns lookup field → the parent fields to load.
Example
apex
public with sharing class ContactPopulator implements BeforeUpdate.Populator, BeforeUpdate.PriorParentQuery {
public Map<SObjectField, TriggerHandler.ParentFields> queryPriorParentsOnBeforeUpdate() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Contact.AccountId => TriggerHandler.ParentFields.with(Account.Name) };
}
public Boolean populateOnBeforeUpdateWhen(TriggerHandler.UpdateRecord record) {
return record.isChanged(Contact.AccountId) && record.getOldParent('Account') != null;
}
public void populateOnBeforeUpdate(TriggerHandler.UpdateRecord record) {
record.put(Contact.Description, 'Moved from ' + ((Account) record.getOldParent('Account')).Name);
}
}cls
public with sharing class OpportunityAccountMoveValidator implements BeforeUpdate.Validator, BeforeUpdate.ParentQuery, BeforeUpdate.PriorParentQuery {
public Map<SObjectField, TriggerHandler.ParentFields> queryParentsOnBeforeUpdate() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Opportunity.AccountId => TriggerHandler.ParentFields.with(Account.Name) };
}
public Map<SObjectField, TriggerHandler.ParentFields> queryPriorParentsOnBeforeUpdate() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Opportunity.AccountId => TriggerHandler.ParentFields.with(Account.Name) };
}
public Boolean errorShouldBeAttachedOnBeforeUpdateWhen(TriggerHandler.UpdateRecord record) {
return record.equals(Opportunity.StageName, 'Closed Won') && record.isChanged(Opportunity.AccountId);
}
public void addErrorOnBeforeUpdate(TriggerHandler.RejectableUpdateRecord record) {
Account previousAccount = (Account) record.getOldParent('Account');
Account currentAccount = (Account) record.getNewParent('Account');
record.addError(
Opportunity.AccountId,
'This Opportunity is Closed Won and stays with ' +
(previousAccount?.Name ?? 'its original Account') +
'. It cannot be moved to ' +
(currentAccount?.Name ?? 'another Account') +
'.'
);
}
}cls
public with sharing class ContactAccountTransferPopulator implements BeforeUpdate.Populator, BeforeUpdate.ParentQuery, BeforeUpdate.PriorParentQuery, BeforeUpdate.RecursionGuard {
public Map<SObjectField, TriggerHandler.ParentFields> queryParentsOnBeforeUpdate() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Contact.AccountId => TriggerHandler.ParentFields.with(Account.Name) };
}
public Map<SObjectField, TriggerHandler.ParentFields> queryPriorParentsOnBeforeUpdate() {
return new Map<SObjectField, TriggerHandler.ParentFields>{ Contact.AccountId => TriggerHandler.ParentFields.with(Account.Name) };
}
public Integer maxRecursionDepthOnBeforeUpdate() {
return 1;
}
public Boolean populateOnBeforeUpdateWhen(TriggerHandler.UpdateRecord record) {
return record.isChanged(Contact.AccountId) && !record.isChangedFrom(Contact.AccountId, null);
}
public void populateOnBeforeUpdate(TriggerHandler.UpdateRecord record) {
Contact contactRecord = (Contact) record.getNewSObject();
Account previousAccount = (Account) record.getOldParent('Account');
Account currentAccount = (Account) record.getNewParent('Account');
String auditLine = Date.today().format() + ' - transferred from ' + (previousAccount?.Name ?? 'a deleted account') + ' to ' + (currentAccount?.Name ?? 'no account') + '.';
record.put(Contact.Description, String.isBlank(contactRecord.Description) ? auditLine : contactRecord.Description + '\n' + auditLine);
}
}Good to Know
- Read with
getOldParent.record.getOldParent('Account')is null when the old lookup was empty or the parent has been deleted since. - Current field values. The previous parent is queried when the trigger runs, so its fields show their values now, not when the record pointed to it.
- Never refreshed. A Populator that changes the lookup changes the current parent, never the previous one.
- One query for both sides. With a ParentQuery on the same lookup, both parents load in one query, and each gets every field either side declared.
- Only need the old Id? Read
getOldSObject(). No add-on is needed.
