BeforeDelete.Validator
Reject a delete before the records are removed, like a validation rule written in Apex.
Signature
apex
public interface Validator extends BeforeDelete.Handler {
Boolean addErrorOnBeforeDeleteWhen(TriggerTypes.DeleteRecord record);
void addErrorOnBeforeDelete(TriggerTypes.RejectableDeleteRecord record);
}addErrorOnBeforeDeleteWhen(record): called once per record in the chunk. Returntrueto reject the record.addErrorOnBeforeDelete(record): called right after its predicate returns true; it must attach an error to the record, or the library throws.
Example
apex
public with sharing class ContactValidator implements BeforeDelete.Validator {
public Boolean addErrorOnBeforeDeleteWhen(TriggerTypes.DeleteRecord record) {
return record.isTrue(Contact.DoNotCall);
}
public void addErrorOnBeforeDelete(TriggerTypes.RejectableDeleteRecord record) {
record.addError('A Do Not Call contact cannot be deleted.');
}
}apex
public with sharing class AccountDeletionGuardValidator implements BeforeDelete.Validator, BeforeDelete.RelatedQuery {
public Map<String, BeforeDelete.RecordsProvider> queryRelatedOnBeforeDelete() {
return new Map<String, BeforeDelete.RecordsProvider>{ 'openOpportunities' => new OpenOpportunitiesProvider() };
}
public Boolean addErrorOnBeforeDeleteWhen(TriggerTypes.DeleteRecord record) {
return record.getRelated('openOpportunities').getFirstWhereKeyEquals(record.getId()) != null;
}
public void addErrorOnBeforeDelete(TriggerTypes.RejectableDeleteRecord record) {
record.addError('Close or move the open opportunities before you delete this account.');
}
private without sharing class OpenOpportunitiesProvider implements BeforeDelete.RecordsProvider {
public List<SObject> query(TriggerTypes.DeleteRecords records) {
return [SELECT Id, AccountId FROM Opportunity WHERE AccountId IN :records.getIds() AND IsClosed = FALSE];
}
public String keyOf(SObject row) {
return ((Opportunity) row).AccountId;
}
}
}Rules
- Block with
record.addError.addError(message)oraddError(field, message)attaches the error to the row being deleted, and its delete fails. - Every qualified record must get an error. If
addErrorOnBeforeDeleteattaches none, the library throws aTriggerTypes.TriggerLibException, even with ContinueOnError. - Errors add up. A record with an error still reaches every later handler in the list.
- Validator wins. A class that also implements
BeforeDelete.Writerruns only as a Validator.
WARNING
A guard must see every row. A with sharing provider misses rows the user cannot see, and a missed row lets the delete through. Never add ContinueOnError to a guard: a swallowed exception lets the delete through too.
Test
apex
@IsTest
static void addErrorOnBeforeDeleteAttachesError() {
// Setup
Account oldAccount = new Account(Id = new TriggerTypes.RandomIdGenerator().get(Account.SObjectType), Name = 'Acme');
// Test
new AccountDeletionGuardValidator().addErrorOnBeforeDelete(new TriggerTypes.TriggerRecord(null, oldAccount));
// Verify
Assert.isTrue(oldAccount.hasErrors(), 'The delete should be blocked.');
}