Skip to content

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.