Skip to content

Instantly share code, notes, and snippets.

@abd3
Created July 28, 2019 19:41
Show Gist options
  • Save abd3/a4a8a6b4476440adcfdea290de47948d to your computer and use it in GitHub Desktop.
Save abd3/a4a8a6b4476440adcfdea290de47948d to your computer and use it in GitHub Desktop.
"Comprehensive Trigger Template" by Mike Leach
This text and the code below, originally came from a blog post from Mike Leach. That blog has subsequently been taken offline, but the contents are shared here for reference.
From Mike Leach:
====
I decided to create a more fluent Trigger template to address the following challenges and prevent me from repeatedly making the same mistakes:
* Bulkification best practices not provisioned by the Trigger creation wizard
* Use of the 7 boolean context variables in code (isInsert, isBefore, etc...) greatly impairs readability and long-term maintainability
* Trigger.old and Trigger.new collections are not available in certain contexts
* Asynchronous trigger support not natively built-in
The solution was to create a mega-Trigger that handles all events and delegates them accordingly to an Apex trigger handler class.
You may want to customize this template to your own style. Here are some design considerations and assumptions in this template:
* Use of traditional event method names on the handler class (OnBeforeInsert, OnAfterInsert)
* Maps are used where they are most relevant
* Objects in map collections cannot be modified, however there is nothing in the compiler to prevent you from trying. Remove them whenever not needed.
* Maps are most useful when triggers modify other records by IDs, so they're included in update and delete triggers
* Encourage use of asynchronous trigger processing by providing pre-built @future methods.
* @future methods only support collections of native types. ID is preferred using this style.
* Avoid use of before/after if not relevant. Example: OnUndelete is simpler than OnAfterUndelete (before undelete is not supported)
* Provide boolean properties for determining trigger context (Is it a Trigger or VF/WebService call?)
* There are no return values. Handler methods are assumed to assert validation rules using addError() to prevent commit.
=================
AccountTrigger.trigger
=================
trigger AccountTrigger on Account (after delete, after insert, after undelete,
after update, before delete, before insert, before update) {
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
if(Trigger.isInsert && Trigger.isBefore){
handler.OnBeforeInsert(Trigger.new);
}
else if(Trigger.isInsert && Trigger.isAfter){
handler.OnAfterInsert(Trigger.new);
AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
}
else if(Trigger.isUpdate && Trigger.isBefore){
handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
}
else if(Trigger.isUpdate && Trigger.isAfter){
handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.newMap);
AccountTriggerHandler.OnAfterUpdateAsync(Trigger.newMap.keySet());
}
else if(Trigger.isDelete && Trigger.isBefore){
handler.OnBeforeDelete(Trigger.old, Trigger.oldMap);
}
else if(Trigger.isDelete && Trigger.isAfter){
handler.OnAfterDelete(Trigger.old, Trigger.oldMap);
AccountTriggerHandler.OnAfterDeleteAsync(Trigger.oldMap.keySet());
}
else if(Trigger.isUnDelete){
handler.OnUndelete(Trigger.new);
}
}
=====================
AccountTriggerHandler.cls
====================
public with sharing class AccountTriggerHandler {
private boolean m_isExecuting = false;
private integer BatchSize = 0;
public AccountTriggerHandler(boolean isExecuting, integer size){
m_isExecuting = isExecuting;
BatchSize = size;
}
public void OnBeforeInsert(Account[] newAccounts){
//Example usage
for(Account newAccount : newAccounts){
if(newAccount.AnnualRevenue == null){
newAccount.AnnualRevenue.addError('Missing annual revenue');
}
}
}
public void OnAfterInsert(Account[] newAccounts){
}
@future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){
//Example usage
List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs];
}
public void OnBeforeUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){
//Example Map usage
Map<ID, Contact> contacts = new Map<ID, Contact>( [select Id, FirstName, LastName, Email from Contact where AccountId IN :accountMap.keySet()] );
}
public void OnAfterUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){
}
@future public static void OnAfterUpdateAsync(Set<ID> updatedAccountIDs){
List<Account> updatedAccounts = [select Id, Name from Account where Id IN :updatedAccountIDs];
}
public void OnBeforeDelete(Account[] accountsToDelete, Map<ID, Account> accountMap){
}
public void OnAfterDelete(Account[] deletedAccounts, Map<ID, Account> accountMap){
}
@future public static void OnAfterDeleteAsync(Set<ID> deletedAccountIDs){
}
public void OnUndelete(Account[] restoredAccounts){
}
public boolean IsTriggerContext{
get{ return m_isExecuting;}
}
public boolean IsVisualforcePageContext{
get{ return !IsTriggerContext;}
}
public boolean IsWebServiceContext{
get{ return !IsTriggerContext;}
}
public boolean IsExecuteAnonymousContext{
get{ return !IsTriggerContext;}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment