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
David_David_ 

From Trigger to Apex Class: Help needed for a simple example

Hey there,

I'm new to APEX and curious about converting Apex Triggers to Apex Classes in order to be in line with the One-Trigger-per-Object pattern. 

Let's say I have the following, very simple Trigger: Whenever an Account gets updated, the number of related contacts gets populated in the custom field Number_of_Contacts__c. The Trigger code looks like this:
 
trigger relatedContacts on Account (before update) {  
   
   List<Contact> relatedContacts = [SELECT Id
                                    FROM   Contact
                                    WHERE  AccountId IN :Trigger.new];
        
   for (Account a : Trigger.new) {
        a.Number_of_Contacts__c = relatedContacts.size();
    }            
    
}
First question: How would the Apex Class for this Trigger look like?

Second question: How shall I reference the Apex Class in the Master Trigger for the Account object? At least I already know where to put it:
trigger MasterOpportunityTrigger on Opportunity (
  before insert, after insert, 
  before update, after update, 
  before delete, after delete) {

  if (Trigger.isBefore) {
    if (Trigger.isInsert) {

    } 
    if (Trigger.isUpdate) {
      // MY TRIGGER WILL GO HERE ;)
    }
    if (Trigger.isDelete) {

    }
  }

  if (Trigger.isAfter) {
    if (Trigger.isInsert) {

    } 
    if (Trigger.isUpdate) {

    }
    if (Trigger.isDelete) {

    }
  }
}

I would highly appreciate if someone could teach me this best practice!

Best, 
David
Best Answer chosen by David_
Maharajan CMaharajan C
Hi David,

Sorry David i have done some of the simple mistakes due to my late night reply :) .

Please find the below updated Class:


Public class accountTriggerHelper
{
    Public static void  handlebeforeupdate (Map<Id,Account> newMap) {  
        Map<Id,Decimal> accmap = new Map<Id,Decimal>();
        List<Account> accContacts = [SELECT Id,Number_of_Contacts__c,(Select Id
                                                                      FROM Contacts) from Account
                                     WHERE Id IN : newMap.keySet()];
        for (Account a : accContacts) {
            accmap.put(a.Id, a.Contacts.size());
        }
        
        for(Account acc : newMap.values())
        {
            acc.Number_of_Contacts__c = accmap.get(acc.Id);
        }
    }
}


And find my below comments for your questions :

1.   why the iterations didn't work with the accContacts List variable which you are referencing in the for loop?  -  See i have to refer the Triiger.New or Trigger.newMap.Values() in for loop then only i can able to override record values in Before update. By mistake i have tried to overide the accContacts list it will not overide the records.

2.  David first for this scenario we have to write the trigger in Contact Object. Because this field have to be updated on Contact Insert , Delete, update. If you are writing the trigger in Account it will fire only when account is got update so the value won't be incremented/Decremented for the new contact insert/delete  for the particular Account until you update the Account. 

Thanks,
Maharajan.C

All Answers

David_David_
EDIT: It must be MasterAccountTrigger on Account of course. :)
Maharajan CMaharajan C
Hi David,

Your trigger and helper class should be like below:

Apex Trigger:

trigger MasterAccountTrigger on Account (before insert, before update,after insert, after update) {
    if (Trigger.isBefore) {
        if(Trigger.isInsert){
        }
        else if(Trigger.isUpdate){
            accountTriggerHelper.handlebeforeupdate(Trigger.newMap);
        }
    }
    else if(Trigger.isAfter) {
        if(Trigger.isInsert) {
        }
        else if(Trigger.isUpdate){
        }
    }
}

=================

Apex Class:

Public class accountTriggerHelper
{
    Public static void  handlebeforeupdate (Map<Id,Account> newMap) {  
   
        List<Account> accContacts = [SELECT Id,Number_of_Contacts__c,(Select Id
                                    FROM Contacts)
                                    WHERE Id IN : newMap.keySet()];
        
        for (Account a : accContacts) {
            a.Number_of_Contacts__c = a.Contacts;
    }            
}
}

Thanks,
Maharajan.C

 
David_David_
Hi Maharajan C,

thanks for your reply! 

Good news and bad news: I tried your code and it didn't work in the first place - I guess the issue was the Apex Class.

I tried your Apex Class which looks like this:
 
Public class accountTriggerHelper
{
    Public static void  handlebeforeupdate (Map<Id,Account> newMap) {  
   
        List<Account> accContacts = [SELECT Id,Number_of_Contacts__c,(Select Id
                                    FROM Contacts)
                                    FROM Account // had to add this ;)
                                    WHERE Id IN : newMap.keySet()];
        
        for (Account a : accContacts) {
            a.Number_of_Contacts__c = a.Contacts;
    }            
}
}
This actually didn't work. While saving a record nothing happend (Trigger and Class were activated).

I 1) changed the collection from Map to List and 2) referenced the variable myAccounts in the for loop and it actually worked:
 

   public static void handlebeforeupdate (List<Account> myAccounts /* aka Trigger.new */ ) {  
   
        List<Contact> accContacts = [SELECT Id                                          
                                     FROM   Contact
                                     WHERE  AccountId IN :myAccounts];
       
        for (Account myAccount : myAccounts) {
            
             myAccount.Number_of_Contacts__c = Decimal.valueOf(accContacts.size());
            
             }  

        }
}

In the Master Trigger I referenced Trigger.new:
 
if (Trigger.isUpdate) {
                
            accountTriggerHelper.handlebeforeupdate(Trigger.new);   
            
            }

Do you think my approach is reasonable? Also, could you be so kind as to check your Class again? I want to understand why you went for this approach. Especially I want to understand why the iterations didn't work with the accContacts List variable which you are referencing in the for loop.

Thanks for your help!
 
Maharajan CMaharajan C
Hi David,

Sorry David i have done some of the simple mistakes due to my late night reply :) .

Please find the below updated Class:


Public class accountTriggerHelper
{
    Public static void  handlebeforeupdate (Map<Id,Account> newMap) {  
        Map<Id,Decimal> accmap = new Map<Id,Decimal>();
        List<Account> accContacts = [SELECT Id,Number_of_Contacts__c,(Select Id
                                                                      FROM Contacts) from Account
                                     WHERE Id IN : newMap.keySet()];
        for (Account a : accContacts) {
            accmap.put(a.Id, a.Contacts.size());
        }
        
        for(Account acc : newMap.values())
        {
            acc.Number_of_Contacts__c = accmap.get(acc.Id);
        }
    }
}


And find my below comments for your questions :

1.   why the iterations didn't work with the accContacts List variable which you are referencing in the for loop?  -  See i have to refer the Triiger.New or Trigger.newMap.Values() in for loop then only i can able to override record values in Before update. By mistake i have tried to overide the accContacts list it will not overide the records.

2.  David first for this scenario we have to write the trigger in Contact Object. Because this field have to be updated on Contact Insert , Delete, update. If you are writing the trigger in Account it will fire only when account is got update so the value won't be incremented/Decremented for the new contact insert/delete  for the particular Account until you update the Account. 

Thanks,
Maharajan.C
This was selected as the best answer
David_David_
Hi Maharajan.C,

thanks a lot for your help! 

Of course you're right: If you want to re-create the behaviour of a roll-up summary you must not write the logic on the Account. It was really just an example - will not apply this to a real life scenario. ;)

I have one last questions regarding handling collections in Classes: As you can see in my post above I was referencing myAccounts in the loop (instead of doing all the Map stuff) - do you think this is reasonable? It actually works. 

Another, even simpler example: Let's say we have an even simpler logic that only sets the Description field to "Thank you Maharajan!" - do you think writing the Class like this would be reasonable? 
 
public static void updateDescriptionField (List<Account> myAccounts /* aka Trigger.new */ ) {  
       
        for (Account myAccount : myAccounts) {
            
             myAccount.Description = 'Thank you Maharajan!';
            
             }  

        }
}

 
Maharajan CMaharajan C
Hi David,

The below code will not work properly while you are performing the bulk update (More than one record). For Example am updating 5 Account records using the data loader each account had 2 contact so the below code have to populate 2 counts in each account but it will populate count as 10 in each account.

See we can use the new account list instead of account Map in Class input param but it will increade some line of additional codes so i have used Triggermap.


public static void handlebeforeupdate (List<Account> myAccounts /* aka Trigger.new */ ) {  
   
        List<Contact> accContacts = [SELECT Id                                          
                                     FROM   Contact
                                     WHERE  AccountId IN :myAccounts];
       
        for (Account myAccount : myAccounts) {
            
             myAccount.Number_of_Contacts__c = Decimal.valueOf(accContacts.size());
            
             }  

        }
}

==================

The below method is fine. Because we are simple updating the Account description with out any related object depedency.

public static void updateDescriptionField (List<Account> myAccounts /* aka Trigger.new */ ) {  
       
        for (Account myAccount : myAccounts) {
            
             myAccount.Description = 'Thank you Maharajan!';
            
             }  

        }
}


Thanks,
Maharajan.C


 
David_David_
Thank you Maharajan, this helped a lot!