Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reformat renatoliveira's patch #30

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This trigger framework bundles a single **TriggerHandler** base class that you c

The base class also provides a secondary role as a supervisor for Trigger execution. It acts like a watchdog, monitoring trigger activity and providing an api for controlling certain aspects of execution and control flow.

But the most important part of this framework is that it's minimal and simple to use.
But the most important part of this framework is that it's minimal and simple to use.

**Deploy to SFDX Scratch Org:**
[![Deploy](https://deploy-to-sfdx.com/dist/assets/images/DeployToSFDX.svg)](https://deploy-to-sfdx.com)
Expand All @@ -33,7 +33,7 @@ In your trigger handler, to add logic to any of the trigger contexts, you only n

```java
public class OpportunityTriggerHandler extends TriggerHandler {

public override void beforeUpdate() {
for(Opportunity o : (List<Opportunity>) Trigger.new) {
// do something
Expand All @@ -45,7 +45,7 @@ public class OpportunityTriggerHandler extends TriggerHandler {
}
```

**Note:** When referencing the Trigger statics within a class, SObjects are returned versus SObject subclasses like Opportunity, Account, etc. This means that you must cast when you reference them in your trigger handler. You could do this in your constructor if you wanted.
**Note:** When referencing the Trigger statics within a class, SObjects are returned versus SObject subclasses like Opportunity, Account, etc. This means that you must cast when you reference them in your trigger handler. You could do this in your constructor if you wanted.

```java
public class OpportunityTriggerHandler extends TriggerHandler {
Expand All @@ -55,7 +55,7 @@ public class OpportunityTriggerHandler extends TriggerHandler {
public OpportunityTriggerHandler() {
this.newOppMap = (Map<Id, Opportunity>) Trigger.newMap;
}

public override void afterUpdate() {
//
}
Expand Down Expand Up @@ -83,7 +83,7 @@ public class OpportunityTriggerHandler extends TriggerHandler {
public OpportunityTriggerHandler() {
this.setMaxLoopCount(1);
}

public override void afterUpdate() {
List<Opportunity> opps = [SELECT Id FROM Opportunity WHERE Id IN :Trigger.newMap.keySet()];
update opps; // this will throw after this update
Expand All @@ -94,43 +94,45 @@ public class OpportunityTriggerHandler extends TriggerHandler {

### Bypass API

What if you want to tell other trigger handlers to halt execution? That's easy with the bypass api:
What if you want to tell other trigger handlers to halt execution? That's easy with the bypass api. Trigger handlers can be bypassed individually, multiple at a time, or globally.

```java
public class OpportunityTriggerHandler extends TriggerHandler {

public override void afterUpdate() {
List<Opportunity> opps = [SELECT Id, AccountId FROM Opportunity WHERE Id IN :Trigger.newMap.keySet()];

Account acc = [SELECT Id, Name FROM Account WHERE Id = :opps.get(0).AccountId];

TriggerHandler.bypass('AccountTriggerHandler');
List<Opportunity> opps = [SELECT Id, AccountId FROM Opportunity WHERE ExpectedRevenue < 100000];

acc.Name = 'No Trigger';
update acc; // won't invoke the AccountTriggerHandler
Account acc = [SELECT Id, Name FROM Account WHERE Id = :opps.get(0).AccountId];

TriggerHandler.clearBypass('AccountTriggerHandler');
// individually
TriggerHandler.bypass('AccountTriggerHandler');
// multiple
//List<String> handlers = new List<String>{'AccountTriggerHandler', 'OpportunityTriggerHandler'}
//TriggerHandler.bypass(handlers);
// globally
//TriggerHandler.bypassAll()

acc.Name = 'With Trigger';
update acc; // will invoke the AccountTriggerHandler
acc.Name = 'No Trigger';
update acc; // won't invoke the AccountTriggerHandler

}
// they can be cleared in the same ways
TriggerHandler.clearBypass('AccountTriggerHandler'); // clear a single one (even if multiple bypassed)
//TriggerHandler.clearBypass(handlers); // clear a list (can be a subset of those bypassed)
//TriggerHandler.clearAllBypasses(); // clear all bypasses, or a global one

}
acc.Name = 'With Trigger';
update acc; // will invoke the AccountTriggerHandler
```

If you need to check if a handler is bypassed, use the `isBypassed` method:
If you need to check if a handler is bypassed, use the `isBypassed` method. This will return true if the indicated handler is being bypassed, or if globalBypass() was called:

```java
if (TriggerHandler.isBypassed('AccountTriggerHandler')) {
// ... do something if the Account trigger handler is bypassed!
}
```

If you want to clear all bypasses for the transaction, simple use the `clearAllBypasses` method, as in:
If you want to clear all bypasses for the transaction, simply use the `clearAllBypasses` method:

```java
// ... done with bypasses!
// ... done with bypasses (including global ones)!

TriggerHandler.clearAllBypasses();

Expand Down
126 changes: 100 additions & 26 deletions src/classes/TriggerHandler.cls
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ public virtual class TriggerHandler {
// static map of handlername, times run() was invoked
private static Map<String, LoopCount> loopCountMap;
private static Set<String> bypassedHandlers;
@TestVisible private static Boolean globalBypass;
@TestVisible private static Boolean showLimits;

// the current context of the trigger, overridable in tests
@TestVisible
Expand All @@ -16,8 +18,10 @@ public virtual class TriggerHandler {
static {
loopCountMap = new Map<String, LoopCount>();
bypassedHandlers = new Set<String>();
globalBypass = false;
showLimits = false;
}

// constructor
public TriggerHandler() {
this.setTriggerContext();
Expand Down Expand Up @@ -60,6 +64,15 @@ public virtual class TriggerHandler {
this.afterUndelete();
}
}

if(showLimits) {
System.debug(LoggingLevel.DEBUG, String.format('{0} on {1} ({2}/{3})', new List<String>{
this.context+'',
getHandlerName(),
Limits.getQueries()+'',
Limits.getLimitQueries()+''
}));
}
}

public void setMaxLoopCount(Integer max) {
Expand All @@ -79,20 +92,81 @@ public virtual class TriggerHandler {
* public static methods
***************************************/

// bypass by string, e.g. TriggerHandler.bypass('AccountTriggerHandler')
public static void bypass(String handlerName) {
TriggerHandler.bypassedHandlers.add(handlerName);
}

// bypass by list, e.g. TriggerHandler.bypass(listOfHandlerStrings)
public static void bypass(List<String> handlersNames) {
TriggerHandler.bypassedHandlers.addAll(handlersNames);
}

// bypass by type, e.g. TriggerHandler.bypass(AccountTriggerHandler.class)
public static void bypass(Type handlerType) {
TriggerHandler.bypass(handlerType.getName());
}

// bypass all handlers (clear bypassedHandlers to prevent confusion)
public static void bypassAll() {
TriggerHandler.bypassedHandlers.clear();
globalBypass = true;
}

public static void clearBypass(String handlerName) {
TriggerHandler.bypassedHandlers.remove(handlerName);
}

public static void clearBypass(List<String> handlersNames) {
TriggerHandler.bypassedHandlers.removeAll(handlersNames);
}

public static void clearBypass(Type handlerType) {
TriggerHandler.clearBypass(handlerType.getName());
}

// a handler is considered bypassed if it was bypassed, or all handlers have been
public static Boolean isBypassed(String handlerName) {
return TriggerHandler.bypassedHandlers.contains(handlerName);
return (globalBypass || TriggerHandler.bypassedHandlers.contains(handlerName));
}

public static Boolean isBypassed(Type handlerType) {
return (globalBypass || TriggerHandler.bypassedHandlers.contains(handlerType.getName()));
}

// return a list of the bypassed handlers
public static List<String> bypassList() {
List<String> bypasses = new List<String>(TriggerHandler.bypassedHandlers);

// bypassAll clears bypassedHandlers, so bypasses is empty here
if(globalBypass) {
bypasses.add('bypassAll');
}

return bypasses;
}

public static void clearAllBypasses() {
TriggerHandler.bypassedHandlers.clear();
if(globalBypass) {
globalBypass = false;
} else {
TriggerHandler.bypassedHandlers.clear();
}
}

public static void showLimits() {
showLimits = true;
}
Comment on lines +157 to +159
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overloaded to allow for calling method with a boolean. This allows toggling off.

Suggested change
public static void showLimits() {
showLimits = true;
}
public static void showLimits() {
showLimits(true);
}
public static void showLimits(Boolean enabled) {
showLimits = enabled;
}


public static void showLimits(Boolean enabled) {
showLimits = enabled;
}

public static Integer getLoopCount(String handlerName) {
if(TriggerHandler.loopCountMap.containsKey(handlerName)) {
return TriggerHandler.loopCountMap.get(handlerName).getCount();
}
return 0;
}

/***************************************
Expand All @@ -112,28 +186,27 @@ public virtual class TriggerHandler {
} else {
this.isTriggerExecuting = true;
}

if((Trigger.isExecuting && Trigger.isBefore && Trigger.isInsert) ||
(ctx != null && ctx == 'before insert')) {
this.context = TriggerContext.BEFORE_INSERT;
} else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) ||
(ctx != null && ctx == 'before update')){
this.context = TriggerContext.BEFORE_UPDATE;
} else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) ||
(ctx != null && ctx == 'before delete')) {
this.context = TriggerContext.BEFORE_DELETE;
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) ||
(ctx != null && ctx == 'after insert')) {
this.context = TriggerContext.AFTER_INSERT;
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) ||
(ctx != null && ctx == 'after update')) {
this.context = TriggerContext.AFTER_UPDATE;
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) ||
(ctx != null && ctx == 'after delete')) {
this.context = TriggerContext.AFTER_DELETE;
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) ||
(ctx != null && ctx == 'after undelete')) {
this.context = TriggerContext.AFTER_UNDELETE;

if(Trigger.isExecuting) {
if(Trigger.isBefore) {
if (Trigger.IsInsert || (ctx != null && ctx == 'before insert')) {
this.context = TriggerContext.BEFORE_INSERT;
} else if (Trigger.IsUpdate || (ctx != null && ctx == 'before update')) {
this.context = TriggerContext.BEFORE_UPDATE;
} else if (Trigger.IsDelete || (ctx != null && ctx == 'before delete')) {
this.context = TriggerContext.BEFORE_DELETE;
}
} else if(Trigger.isAfter) {
if (Trigger.IsInsert || (ctx != null && ctx == 'after insert')) {
this.context = TriggerContext.AFTER_INSERT;
} else if (Trigger.IsUpdate || (ctx != null && ctx == 'after update')) {
this.context = TriggerContext.AFTER_UPDATE;
} else if (Trigger.IsDelete || (ctx != null && ctx == 'after delete')) {
this.context = TriggerContext.AFTER_DELETE;
} else if (Trigger.IsUndelete || (ctx != null && ctx == 'after undelete')) {
this.context = TriggerContext.AFTER_UNDELETE;
}
}
}
}

Expand All @@ -156,7 +229,7 @@ public virtual class TriggerHandler {
if(!this.isTriggerExecuting || this.context == null) {
throw new TriggerHandlerException('Trigger handler called outside of Trigger execution');
}
return !TriggerHandler.bypassedHandlers.contains(getHandlerName());
return (!globalBypass && !TriggerHandler.bypassedHandlers.contains(getHandlerName()));
}

@TestVisible
Expand Down Expand Up @@ -194,6 +267,7 @@ public virtual class TriggerHandler {
private Integer max;
private Integer count;

// constructor
public LoopCount() {
this.max = 5;
this.count = 0;
Expand Down