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
Zach DelacroixZach Delacroix 

Help with Trigger Handler

Hi Experts!

I'm a newbie and I've created a Trigger below with the help of awesome people here. This works fine but my friend suggested to study about the Trigger Handler so we can limit triggers in object and my code will be easier to debug. I want to learn the Best Practices on this so please help and advise how to put this with Class. 
 
trigger UpdateHourlyRate on Job__c (Before insert, Before update) {

    Set<Id> AccID = new Set<Id>();
    Set<String> JobType = new Set<String>();
    
    Map<String,Rate_card__c> RateCardMap = new Map<String,Rate_card__c>();
    
    for (Job__c Job: Trigger.new){AccID.add(Job.Account__c); JobType.add(Job.Type__c);}
    
    For(Rate_Card__c RateCard : [SELECT Id, Hourly_Rate__c, Account__c, Type__c 
                                 FROM Rate_card__c 
                                 WHERE Account__c IN: AccID]){
								 	
                                     RateCardMap.put(RateCard.Account__c + RateCard.Type__c, RateCard);
  
                                 }
 
	For (Job__c Jobs: Trigger.new ){
            
            if(RateCardMap.containsKey(Jobs.Account__c+Jobs.Type__c)){ 
    
                Jobs.Hourly_Rate__c = RateCardMap.get(Jobs.Account__c + Jobs.Type__c).Hourly_Rate__c;

    }
}

I'm not sure how to start though. basically, I thought of building a class like below.
 
public class JobHourlyRateUpdate {

    Set<Id> AccID = new Set<Id>();
    
    public Static void computeHourlyRate(List<Job__c> Jobs){
        
            For(Job__c Job:Jobs){
                AccID.add(Job.Account__c);
            }
            
            Map<String,Rate_card__c> RateCardMap = new Map<String,Rate_card__c>();
            
            For(Rate_Card__c RateCard : [SELECT Id, Hourly_Rate__c, Account__c, Type__c 
                                     FROM Rate_card__c 
                                     WHERE Account__c IN: AccID]){
                                        
                                         RateCardMap.put(RateCard.Account__c + RateCard.Type__c, RateCard);
      
                                     }
        
    }
}


Here's what my code does:

When Job Record is Created or Updated, find Rate Card from Account (Related List) and update the Job's Hourly Rate if the Ratecard has the same Type as the Job.

Result Should be something like below.

User-added image


It would be fantastic if there are documents you can reffer me so I can start learning about this :)

Thanks in Advance!
Best Answer chosen by Zach Delacroix
Sunil02KumarSunil02Kumar
Hi Zach,

It is always a best practice to write a single trigger on object and process all operation in apex class (we called it as handler) instead of 
writing logic in trigger. In handler you can specify the order of event based on different context variables. If you write many triggers in on object, 
then you cannot control the order in which triggers gets executed.

For best practice, use below suggestion:

1. Pass all trigger context variables to static method (say "OperationManager")in handler class.
2. Create different static methods for different operations which you need to perform.
3. Control the order of different operation in "OperationManager" static method based on context variables.
4. Also you can create different apex classes and call them from "OperationManager" static method.

For example, below is modified code as per best practice.
Apex trigger code:
 
trigger JobTrigger on Job__c (after delete, after insert, after undelete, after update, before delete, before insert, before update)
{
    String errMsg = JobTriggerHandler.OperationManager(trigger.new, trigger.old, trigger.newMap,trigger.oldMap,
                trigger.isBefore,trigger.isAfter,trigger.isInsert,trigger.isUpdate,trigger.isDelete,   trigger.isUndelete,trigger.isExecuting);

    // If error occured, then display it to user
    if (errMsg != null) {
        if (trigger.isInsert || trigger.isUpdate || trigger.isUndelete) { trigger.new[0].addError(errMsg); } else { trigger.old[0].addError(errMsg); }
    }
}



Apex class(Handler):
 
public with sharing class JobTriggerHandler {
     // Main entry point for this handler.
    public static String OperationManager(list<Job__c> newList, list<Job__c> oldList, map<Id, Job__c> newMap,map<Id, Job__c> oldMap,
        boolean isBefore,boolean isAfter, boolean isInsert, boolean isUpdate,boolean isDelete,boolean isUndelete, boolean isExecuting) {
        String errMsg;
        try {
            // Only run a method if previous methods didn't fail
            //As you want to perform operation on before insert and before update
            if (isBefore && (isUpdate || isInsert)  && errMsg == null) {
                // Find hourly rate by calling static method defined below
                errMsg = computeHourlyRate(newList);
            }
            //here you can call any other static methods or some other class method to perform desired operations
            //keep on adding the other logic which needs to be processed through trigger
            //through context variables you can control their execution
        } catch (Exception ex) { 
            errMsg = ex.getMessage() + ' at ' + ex.getLineNumber().format();
        }
        return errMsg;
    }
    
    //Below method will compute hourly rate
    public static String computeHourlyRate(list<Job__c> newList) {
        String errMsg;
        try {
            Set<Id> AccID = new Set<Id>();
            Set<String> JobType = new Set<String>();
            Map<String,Rate_card__c> RateCardMap = new Map<String,Rate_card__c>();
            for (Job__c Job: newList){
                AccID.add(Job.Account__c); 
                JobType.add(Job.Type__c);
            }
            For(Rate_Card__c RateCard : [SELECT Id, Hourly_Rate__c, Account__c, Type__c FROM Rate_card__c  WHERE Account__c IN: AccID]){
                RateCardMap.put(RateCard.Account__c + RateCard.Type__c, RateCard);
            }
            For (Job__c Jobs: newList){
                if(RateCardMap.containsKey(Jobs.Account__c+Jobs.Type__c)){ 
                    Jobs.Hourly_Rate__c = RateCardMap.get(Jobs.Account__c + Jobs.Type__c).Hourly_Rate__c;
                }
            }
        } catch (Exception ex) { errMsg = ex.getMessage(); }
        return errMsg;
    }
}


Refer below URL for Apex Trigger Best Practices 
https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices (https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices" target="_blank)

Hope this will help you.
[If it solves your problem, please mark it as solution]

Thanks,
Sunil Kumar

All Answers

Mudasir WaniMudasir Wani
Hello,

You can follow the beloe link.
https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices
 
//Your trigger code sholud look like 

trigger UpdateHourlyRate on Job__c (Before insert, Before update) {
	JobHourlyRateUpdate.computeHourlyRate(trigger.New);
}


Please mark this as solution if this solves your problem, So that if anyone has this issue this post can help
 
Sunil02KumarSunil02Kumar
Hi Zach,

It is always a best practice to write a single trigger on object and process all operation in apex class (we called it as handler) instead of 
writing logic in trigger. In handler you can specify the order of event based on different context variables. If you write many triggers in on object, 
then you cannot control the order in which triggers gets executed.

For best practice, use below suggestion:

1. Pass all trigger context variables to static method (say "OperationManager")in handler class.
2. Create different static methods for different operations which you need to perform.
3. Control the order of different operation in "OperationManager" static method based on context variables.
4. Also you can create different apex classes and call them from "OperationManager" static method.

For example, below is modified code as per best practice.
Apex trigger code:
 
trigger JobTrigger on Job__c (after delete, after insert, after undelete, after update, before delete, before insert, before update)
{
    String errMsg = JobTriggerHandler.OperationManager(trigger.new, trigger.old, trigger.newMap,trigger.oldMap,
                trigger.isBefore,trigger.isAfter,trigger.isInsert,trigger.isUpdate,trigger.isDelete,   trigger.isUndelete,trigger.isExecuting);

    // If error occured, then display it to user
    if (errMsg != null) {
        if (trigger.isInsert || trigger.isUpdate || trigger.isUndelete) { trigger.new[0].addError(errMsg); } else { trigger.old[0].addError(errMsg); }
    }
}



Apex class(Handler):
 
public with sharing class JobTriggerHandler {
     // Main entry point for this handler.
    public static String OperationManager(list<Job__c> newList, list<Job__c> oldList, map<Id, Job__c> newMap,map<Id, Job__c> oldMap,
        boolean isBefore,boolean isAfter, boolean isInsert, boolean isUpdate,boolean isDelete,boolean isUndelete, boolean isExecuting) {
        String errMsg;
        try {
            // Only run a method if previous methods didn't fail
            //As you want to perform operation on before insert and before update
            if (isBefore && (isUpdate || isInsert)  && errMsg == null) {
                // Find hourly rate by calling static method defined below
                errMsg = computeHourlyRate(newList);
            }
            //here you can call any other static methods or some other class method to perform desired operations
            //keep on adding the other logic which needs to be processed through trigger
            //through context variables you can control their execution
        } catch (Exception ex) { 
            errMsg = ex.getMessage() + ' at ' + ex.getLineNumber().format();
        }
        return errMsg;
    }
    
    //Below method will compute hourly rate
    public static String computeHourlyRate(list<Job__c> newList) {
        String errMsg;
        try {
            Set<Id> AccID = new Set<Id>();
            Set<String> JobType = new Set<String>();
            Map<String,Rate_card__c> RateCardMap = new Map<String,Rate_card__c>();
            for (Job__c Job: newList){
                AccID.add(Job.Account__c); 
                JobType.add(Job.Type__c);
            }
            For(Rate_Card__c RateCard : [SELECT Id, Hourly_Rate__c, Account__c, Type__c FROM Rate_card__c  WHERE Account__c IN: AccID]){
                RateCardMap.put(RateCard.Account__c + RateCard.Type__c, RateCard);
            }
            For (Job__c Jobs: newList){
                if(RateCardMap.containsKey(Jobs.Account__c+Jobs.Type__c)){ 
                    Jobs.Hourly_Rate__c = RateCardMap.get(Jobs.Account__c + Jobs.Type__c).Hourly_Rate__c;
                }
            }
        } catch (Exception ex) { errMsg = ex.getMessage(); }
        return errMsg;
    }
}


Refer below URL for Apex Trigger Best Practices 
https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices (https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices" target="_blank)

Hope this will help you.
[If it solves your problem, please mark it as solution]

Thanks,
Sunil Kumar
This was selected as the best answer
Zach DelacroixZach Delacroix
Thank you so much Sunil!

I tried this in my test and It worked perfectly! However, I'm still having difficulty with it and don't fully understand how it really works.
I will read more about Trigger Handler and I'll make your code as my guide :) This is where I will start learning.. You're the best!

Thanks to @Mudasir as well for the Idea..

\m/