function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Rashed Yaqubi 10Rashed Yaqubi 10 

Trigger Handler for the following:

Hi there, I am relatively new to apex coding and need to write a trigger handler for the following Trigger.  I understand the general logic and reason for why I need to do it, but unsure on how exacty it needs to be done. i.e. do I leave my definition of the ID sets in the trigger and move everything else to the class? Do I need to pass anything from the trigger to the class when I call it (specially if the id sets are defined/declared in the trigger)? Your help is greatly appreciated!

trigger AgreementHasAttachment on ContentDocumentLink (after insert) {
    Set<Id> setParentId = new Set<Id>();
    Set<Id> setGrandParentId = new Set<Id>();
      
    for (ContentDocumentLink cdl : trigger.new)  {
        setParentId.add(cdl.LinkedEntityId);      
    }
    
    List<Agreement__c> Agreementlst = new List<Agreement__c>();
    Agreementlst = [select Id, HasAttachment__c, MDU_Referral_Contact__c from Agreement__c where Id IN :setParentId];    
    For (Agreement__c a : Agreementlst) 
    {
        a.HasAttachment__c = True;
        setGrandParentId.add(a.MDU_Referral_Contact__c);
    }
    
    List<Contact> Contlst = new List<Contact>();
    Contlst = [Select Id, Contract_Uploaded__c from Contact where Id IN :setGrandParentId];
    For (Contact c : Contlst){
        c.Contract_Uploaded__c = true;
    }
    
    if(Agreementlst.size() > 0) update Agreementlst;
    if(Contlst.size() > 0) update Contlst;
}
Ankit Kalsara 6Ankit Kalsara 6
Hi Rashed,

If you want to move the entire trigger code in the handler class, you can do the following.

Step 1: write your trigger with below code.
trigger AgreementHasAttachment on ContentDocumentLink (after insert) {
          new AgreementHasAttachmentHandler.run();
}

Step 2: create the class AgreementHasAttachmentHandler
public class AgreementHasAttachmentHandler extends TriggerHandler{
   // variables
   private LIst<ContentDocumentLink>  triggerNew = (ContentDocumentLink[]) Trigger.new;

   // constructor
    public AgreementHasAttachmentHandler(){}

   // override trigger methods
   public override void afterInsert(){
          for(ContentDocumentLink cdl : triggerNew){
          }
   }


Let me know if this is the soluition helps you.
Rashed Yaqubi 10Rashed Yaqubi 10
Thank you Ankit.  Would you be able to provide me the complete trigger and handler using my code above? I want to have it for reference and make sense of the syntax slowly at my own pace.  Thank you!
Ankit Kalsara 6Ankit Kalsara 6
Sure, PFB code.

Trigger.
trigger AgreementHasAttachment on ContentDocumentLink (after insert) {
          new AgreementHasAttachmentHandler.run();
}

Class
public class AgreementHasAttachmentHandler extends TriggerHandler{
   // variables
   private List<ContentDocumentLink> triggerNew = (ContentDocumentLink[]) Trigger.new;

   // constructor
    public AgreementHasAttachmentHandler(){
    }

   // override trigger methods
   public override void afterInsert(){
     for(ContentDocumentLink cdl : triggerNew){
     }

     List<Agreement__c> Agreementlst = new List<Agreement__c>();
     Agreementlst = [SELECT Id, HasAttachment__c, MDU_Referral_Contact__c FROM Agreement__c WHERE Id IN :setParentId];
     for (Agreement__c a : Agreementlst)
     {
         a.HasAttachment__c = true;
         setGrandParentId.add(a.MDU_Referral_Contact__c);
     }

     List<Contact> Contlst = new List<Contact>();
     Contlst = [SELECT Id, Contract_Uploaded__c FROM Contact WHERE Id IN :setGrandParentId];
     for (Contact c : Contlst){
         c.Contract_Uploaded__c = true;
     }

     if(Agreementlst.size() > 0)
     UPDATE Agreementlst;
     if(Contlst.size() > 0)
     UPDATE Contlst;

   }

}

Please mark this as best answer if it resolves your query.

Thanks.
 
Rashed Yaqubi 10Rashed Yaqubi 10
Thank you Ankit,  I will give this a try and mark this as best answer once I confirm that it works. 

I noticed that the declaration of the id sets (setParentId and setGrandParentId) is not visible anywhere .  Is this not required or did you miss it?
Ankit Kalsara 6Ankit Kalsara 6
I missed it. PFB revised code.
public class AgreementHasAttachmentHandler extends TriggerHandler{
   // variables
   private List<ContentDocumentLink> triggerNew = (ContentDocumentLink[]) Trigger.new;

   // constructor
    public AgreementHasAttachmentHandler(){
    }

   // override trigger methods
   public override void afterInsert(){
     Set<Id> setParentId          = new Set<Id>();
     Set<Id> setGrandParentId = new Set<Id>();
     for(ContentDocumentLink cdl : triggerNew){
     }

     List<Agreement__c> Agreementlst = new List<Agreement__c>();
     Agreementlst = [SELECT Id, HasAttachment__c, MDU_Referral_Contact__c FROM Agreement__c WHERE Id IN :setParentId];
     for (Agreement__c a : Agreementlst)
     {
         a.HasAttachment__c = true;
         setGrandParentId.add(a.MDU_Referral_Contact__c);
     }

     List<Contact> Contlst = new List<Contact>();
     Contlst = [SELECT Id, Contract_Uploaded__c FROM Contact WHERE Id IN :setGrandParentId];
     for (Contact c : Contlst){
         c.Contract_Uploaded__c = true;
     }

     if(Agreementlst.size() > 0)
     UPDATE Agreementlst;
     if(Contlst.size() > 0)
     UPDATE Contlst;

   }

}
Rashed Yaqubi 10Rashed Yaqubi 10

Hi Ankit, I am having trouble compiling the code.  Please see below errors: 

Trigger error:
User-added image

Class error:
User-added image

 

Ankit Kalsara 6Ankit Kalsara 6
Hi Rashed,

I have missed the parenthesis in the trigger code which I have provided.

Please update the trigger and it will work.
 
trigger AgreementHasAttachment on ContentDocumentLink (after insert){
    
    new AgreementHasAttachmentHandler().run();

}

 
Rashed Yaqubi 10Rashed Yaqubi 10

Sorry Ankit, but it is still not compiling

Error on Trigger: Method does not exist or incorrect signature: void run() from the type AgreementHasAttachmentHandler (line2)
trigger AgreementHasAttachment on ContentDocumentLink (after insert) {
    new AgreementHasAttachmentHandler().run();
}

-----------------------

Error on Class: @Override specified for non-overriding method: void AgreementHasAttachmentHandler.afterInsert() (Line 8)
public class AgreementHasAttachmentHandler {
    
    private List<ContentDocumentLink> triggerNew = (ContentDocumentLink[]) Trigger.new;
    
    public AgreementHasAttachmentHandler(){
    }

    public override void afterInsert(){
        for(ContentDocumentLink cdl : triggerNew){
         }

        List<Agreement__c> Agreementlst = new List<Agreement__c>();
        Agreementlst = [SELECT Id, HasAttachment__c, MDU_Referral_Contact__c FROM Agreement__c WHERE Id IN :setParentId];
           for (Agreement__c a : Agreementlst){
            a.HasAttachment__c = true;
            setGrandParentId.add(a.MDU_Referral_Contact__c);
        }

        List<Contact> Contlst = new List<Contact>();
        Contlst = [SELECT Id, Contract_Uploaded__c FROM Contact WHERE Id IN :setGrandParentId];
        for (Contact c : Contlst){
            c.Contract_Uploaded__c = true;
        }

         if(Agreementlst.size() > 0) UPDATE Agreementlst;
         if(Contlst.size() > 0) UPDATE Contlst;

    }
}

Ankit Kalsara 6Ankit Kalsara 6
I do not any error in my system.

User-added image

User-added image

I have commented code inside trigger since I do not have custom object "Agreement" in my org.
Rashed Yaqubi 10Rashed Yaqubi 10
Strange.. can't seem to figure out why it is doing that.
Ankit Kalsara 6Ankit Kalsara 6
Hi Rashed,

Can you add below class and compile your trigger ?
 
public virtual class TriggerHandler {

    // static map of handlername, times run() was invoked
    private static Map<String, LoopCount> loopCountMap;
    private static Set<String> bypassedHandlers;

    // the current context of the trigger, overridable in tests
    @TestVisible
    private TriggerContext context;

    // the current context of the trigger, overridable in tests
    @TestVisible
    private Boolean isTriggerExecuting;

    // static initialization
    static {
        loopCountMap = new Map<String, LoopCount>();
        bypassedHandlers = new Set<String>();
    }

    // constructor
    public TriggerHandler() {
        this.setTriggerContext();
    }

    /***************************************
     * public instance methods
     ***************************************/

    // main method that will be called during execution
    public void run() {

        if(!validateRun()) return;

        addToLoopCount();

        // dispatch to the correct handler method
        if(this.context == TriggerContext.BEFORE_INSERT) {
            this.beforeInsert();
        } else if(this.context == TriggerContext.BEFORE_UPDATE) {
            this.beforeUpdate();
        } else if(this.context == TriggerContext.BEFORE_DELETE) {
            this.beforeDelete();
        } else if(this.context == TriggerContext.AFTER_INSERT) {
            this.afterInsert();
        } else if(this.context == TriggerContext.AFTER_UPDATE) {
            this.afterUpdate();
        } else if(this.context == TriggerContext.AFTER_DELETE) {
            this.afterDelete();
        } else if(this.context == TriggerContext.AFTER_UNDELETE) {
            this.afterUndelete();
        }

    }

    public void setMaxLoopCount(Integer max) {
        String handlerName = getHandlerName();
        if(!TriggerHandler.loopCountMap.containsKey(handlerName)) {
            TriggerHandler.loopCountMap.put(handlerName, new LoopCount(max));
        } else {
            TriggerHandler.loopCountMap.get(handlerName).setMax(max);
        }
    }

    public void clearMaxLoopCount() {
        this.setMaxLoopCount(-1);
    }

    /***************************************
     * public static methods
     ***************************************/

    public static void bypass(String handlerName) {
        TriggerHandler.bypassedHandlers.add(handlerName);
    }

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

    public static Boolean isBypassed(String handlerName) {
        return TriggerHandler.bypassedHandlers.contains(handlerName);
    }

    public static void clearAllBypasses() {
        TriggerHandler.bypassedHandlers.clear();
    }

    /***************************************
     * private instancemethods
     ***************************************/

    @TestVisible
    private void setTriggerContext() {
        this.setTriggerContext(null, false);
    }

    @TestVisible
    private void setTriggerContext(String ctx, Boolean testMode) {
        if(!Trigger.isExecuting && !testMode) {
            this.isTriggerExecuting = false;
            return;
        } 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;
        }
    }

    // increment the loop count
    @TestVisible
    private void addToLoopCount() {
        String handlerName = getHandlerName();
        if(TriggerHandler.loopCountMap.containsKey(handlerName)) {
            Boolean exceeded = TriggerHandler.loopCountMap.get(handlerName).increment();
            if(exceeded) {
                Integer max = TriggerHandler.loopCountMap.get(handlerName).max;
                throw new TriggerHandlerException('Maximum loop count of ' + String.valueOf(max) + ' reached in ' + handlerName);
            }
        }
    }

    // make sure this trigger should continue to run
    @TestVisible
    private Boolean validateRun() {
        if(!this.isTriggerExecuting || this.context == null) {
            throw new TriggerHandlerException('Trigger handler called outside of Trigger execution');
        }
        if(TriggerHandler.bypassedHandlers.contains(getHandlerName())) {
            return false;
        }
        return true;
    }

    @TestVisible
    private String getHandlerName() {
        return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':'));
    }

    /***************************************
     * context methods
     ***************************************/

    // context-specific methods for override
    @TestVisible
    protected virtual void beforeInsert(){}
    @TestVisible
    protected virtual void beforeUpdate(){}
    @TestVisible
    protected virtual void beforeDelete(){}
    @TestVisible
    protected virtual void afterInsert(){}
    @TestVisible
    protected virtual void afterUpdate(){}
    @TestVisible
    protected virtual void afterDelete(){}
    @TestVisible
    protected virtual void afterUndelete(){}

    /***************************************
     * inner classes
     ***************************************/

    // inner class for managing the loop count per handler
    @TestVisible
    private class LoopCount {
        private Integer max;
        private Integer count;

        public LoopCount() {
            this.max = 5;
            this.count = 0;
        }

        public LoopCount(Integer max) {
            this.max = max;
            this.count = 0;
        }

        public Boolean increment() {
            this.count++;
            return this.exceeded();
        }

        public Boolean exceeded() {
            if(this.max < 0) return false;
            if(this.count > this.max) {
                return true;
            }
            return false;
        }

        public Integer getMax() {
            return this.max;
        }

        public Integer getCount() {
            return this.count;
        }

        public void setMax(Integer max) {
            this.max = max;
        }
    }

    // possible trigger contexts
    @TestVisible
    private enum TriggerContext {
        BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE,
        AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE,
        AFTER_UNDELETE
    }

    // exception class
    public class TriggerHandlerException extends Exception {}

}