Dog Image

Mastering Apex Triggers: A Step-by-Step Guide

In Salesforce, triggers play a crucial role in automating business processes by executing custom actions before or after events such as insertions, updates, or deletions of records. Apex triggers allow developers to add complex logic to record operations, ensuring data integrity and enabling seamless integration with other systems. Understanding various trigger scenarios is essential for implementing efficient and effective solutions in Salesforce. This is part four in the Trigger Scenarios in APEX series. In this post, we will explore different trigger scenarios in Apex, providing practical examples and best practices to help you leverage triggers for automating workflows, enforcing validation rules, and maintaining data consistency. Whether you’re a seasoned developer or just starting with Salesforce, these scenarios will equip you with the knowledge to handle common business requirements and optimize your Salesforce applications. Let’s dive in!

# 1 ~ Create a trigger to update the ‘Contact_Names__c’ custom field on the Account record with a semicolon-separated list of all associated Contact names whenever a Contact record is inserted or updated.

Maintaining accurate and up-to-date information across related Salesforce records is crucial for effective data management. One common scenario is the need to aggregate details from child records, such as Contact names, into a custom field on the parent Account record. This consolidated view can enhance reporting, simplify data analysis, and improve usability for Salesforce users.

In this blog post, we’ll explore how to implement an Apex trigger that automatically updates the Contact_Names__c custom field on an Account whenever a Contact is inserted or updated. The field will display a semicolon-separated list of all associated Contact names. This solution demonstrates the power of triggers to ensure data consistency while highlighting best practices for bulk operations and Governor Limits compliance.

# Apex Class:

public class AccountUpdateonContactInsertion {
	public static void accUpdateOnConInsertionMenthod(List<Contact> conList){
		Set<Id> accIds = New Set<Id>();
		if(!conList.isEmpty()){
			for(Contact c : conList){
				if(c.AccountId != Null){
					accIds.add(c.AccountId);
				}
			}
		}
		
		List<Contact> existingConList = [SELECT Id, Name, AccountId FROM Contact WHERE AccountId != Null AND AccountId IN :accIds];
		
		Map<Id, String> accConMap = New Map<Id, String>();
		
		if(!existingConList.isEmpty()){
			for(Contact con : existingConList){
				if(accConMap.containsKey(con.AccountId)){
					List<String> nameStrList = New List<String>();
					nameStrList.add(accConMap.get(con.AccountId));
          nameStrList.add(con.Name);
					accConMap.put(con.AccountId, String.join(nameStrList, '; '));
				} else {
					accConMap.put(con.AccountId, con.Name);
				}
			}
		}
		
		List<Account> accList = [SELECT Id, Name FROM Account WHERE Id IN :accConMap.keySet()];
		
		if(!accList.isEmpty()){
			for(Account acc : accList){
				if(accConMap.containsKey(acc.Id)){
					acc.Contact_Names__c = accConMap.get(acc.Id);
				}
			}
			if(!accList.isEmpty()){
				update accList;
			}
		}
	}
}
Apex

# Apex Trigger:

trigger AccountUpdateonContactInsertionTrigger on Contact (after insert, after delete, after undelete) {
    if(Trigger.isAfter){
        if(Trigger.isInsert || Trigger.isUndelete){
            AccountUpdateonContactInsertion.accUpdateOnConInsertionMenthod(Trigger.new);
        } else if(Trigger.isDelete){
            AccountUpdateonContactInsertion.accUpdateOnConInsertionMenthod(Trigger.old);
        }
    }
}
Apex

# Test Class:

@isTest
public class AccountUpdateonContactInsertionTest {
    @testSetup
    public static void setUpData(){
        Account a = New Account();
        a.Name = 'Test Account';
        insert a;
        
        List<Contact> conList = New List<Contact>();
        conList.add(New Contact(FirstName='Test', LastName='Con1', AccountId=a.Id));
        conList.add(New Contact(FirstName='Test', LastName='Con2', AccountId=a.Id));
        insert conList;
    }
    
    @isTest
    public static void accUpdateOnConInsertionMenthodTest1(){
        Account acc = [SELECT Id, Name FROM Account WHERE Name='Test Account' LIMIT 1];
        
        List<Contact> cList = New List<Contact>();
        cList.add(New Contact(FirstName='Test', LastName='Con3', AccountId=acc.Id));
        insert cList;
        
        Test.startTest();
        AccountUpdateonContactInsertion.accUpdateOnConInsertionMenthod(cList);
        Test.stopTest();
    }
    
    @isTest
    public static void accUpdateOnConInsertionMenthodTest2(){
        Account acc = [SELECT Id, Name FROM Account WHERE Name='Test Account' LIMIT 1];
        
        List<Contact> cList = New List<Contact>();
        cList = [SELECT Id, Name, AccountId FROM Contact WHERE LastName='Con2'];
        delete cList;
        
        Test.startTest();
        AccountUpdateonContactInsertion.accUpdateOnConInsertionMenthod(cList);
        Test.stopTest();
    }
}
Apex

# 2 ~ Implement a trigger that, upon the insertion of a new Product record, automatically creates a duplicate Product record as a child of the original Product. The trigger should prevent infinite recursion by incorporating appropriate checks.

In Salesforce, automating record creation can streamline processes and reduce manual effort. A common use case is the need to create related records automatically when a specific record is inserted. For example, when a new Product record is added, you might want to create a duplicate Product as its child to represent a linked variation or complementary product.

This requirement demonstrates how to implement an Apex trigger to achieve this functionality. The trigger ensures that a child duplicate Product is created upon the insertion of a new Product record. To prevent infinite recursion, which can occur when triggers inadvertently call themselves repeatedly, the implementation includes appropriate checks. This solution highlights how to handle recursive triggers effectively while maintaining data integrity and adhering to Salesforce’s best practices.

# Apex Class:

public class CreateDuplicateProductOnProductInsertion {   
    public static void createDuplicateProductMethod(List<Product2> productList){
        List<Product2> newProductList = New List<Product2>();
        if(!productList.isEmpty()){
            for(Product2 p : productList){
                if(p.SourceProductId == Null){
                    Product2 dP = New Product2();
                    dP.Name = p.Name + ' Duplicate';
                    dP.SourceProductId = p.Id;
                    newProductList.add(dP);
                }
            }
        }
        
        if(!newProductList.isEmpty()){
            insert newProductList;
        }
    }
}
Apex

# Apex Trigger:

trigger CreateDuplicateProductOnProductInsertionTrigger on Product2 (after insert) {
    if(Trigger.isAfter && Trigger.isInsert){
        CreateDuplicateProductOnProductInsertion.createDuplicateProductMethod(Trigger.new);
    }
}
Apex

# Test Class:

@isTest
public class CreateDuplicateProductTest {
    @testSetup
    public static void setUpData(){
        List<Product2> productList = New List<Product2>();
        productList.add(New Product2(Name = 'Test Product1'));
                productList.add(New Product2(Name = 'Test Product2'));

        insert productList;
    }
    
    @isTest
    public static void accUpdateOnConInsertionMenthodTest(){
        List<Product2> pList = [SELECT Id, SourceProductId, Name FROM Product2];
        
        Test.startTest();
        CreateDuplicateProductOnProductInsertion.createDuplicateProductMethod(pList);
        Test.stopTest();
    }
}
Apex

# 3 ~ Develop an Apex trigger that diligently monitors and records all modifications to the ‘Rating’ field on the Account object. This historical log should meticulously document the previous and current values of the ‘Rating’ field, as well as the precise timestamp of each change. Implement the trigger in a manner that effectively handles bulk updates to Account records while adhering to all relevant Salesforce Governor Limits.

In Salesforce, understanding changes to critical data is essential for maintaining transparency and auditing purposes. While Salesforce provides built-in field history tracking, there are scenarios where you might need a more customized solution to monitor changes in specific fields, such as the Rating field on the Account object.

This scenario explores how to implement an Apex trigger that captures every modification to the Rating field, logging both the previous and current values along with a timestamp. The solution is designed to handle bulk updates efficiently while respecting Salesforce’s Governor Limits, ensuring robust and scalable performance in dynamic environments. Whether you’re auditing changes for compliance or analyzing trends in account ratings, this approach provides a tailored and reliable way to track field history.

The scenario necessitates the creation of a custom object named ‘Historical_Log__c‘ to maintain a historical record of ‘Rating’ field changes on Account records. This object should include fields to store the previous (‘Old_Rating_Value__c‘) and current (‘New_Rating_Value__c‘) values of the ‘Rating’, a lookup field to the corresponding ‘Account’ record (‘Account__c‘), and a ‘Time_stamp__c‘ field to capture the exact time of the modification.

# Apex Class:

public class CreateHistoricalLogsOnAccRatingUpdate {
    public static void createHistoricalLogMethod(Map<Id, Account> accMap, Map<Id, Account> accOldMap){
        List<Historical_Log__c> hlList = New List<Historical_Log__c>();
        if(accMap.values().isEmpty() || accOldMap.values().isEmpty()) return;//New way of writing if-else condition (no need of {} curly brackets)
        for(Account acc : accMap.values()){
            if(acc.Rating != accOldMap.get(acc.Id).Rating){
                Historical_Log__c hl = New Historical_Log__c();
                hl.Old_Rating_Value__c = accOldMap.get(acc.Id).Rating;
                hl.New_Rating_Value__c = acc.Rating;
                hl.Account__c = acc.Id;
                hl.Time_Stamp__c = acc.LastModifiedDate;
                hlList.add(hl);
            }
        }
        
        if(!hlList.isEmpty()){
            insert hlList;
        }
    }
}
Apex

# Apex Trigger:

trigger CreateHistoricalLogsOnAccRatingUpdateTrigger on Account (after update) {
    if(Trigger.isAfter && Trigger.isUpdate){
        CreateHistoricalLogsOnAccRatingUpdate.createHistoricalLogMethod(Trigger.newMap, Trigger.oldMap);
    }
}
Apex

# Test Class:

@isTest
public class CreateHistoryLogsOnAccRatingUpdateTest {
    @testSetup
    public static void setUpData(){
        Account a = New Account();
        a.Name = 'Test Account';
        a.Rating = 'Hot';
        insert a;
    }
    
    @isTest
    public static void createHistoricalLogMethodTest(){
        Account acc = [SELECT Id, Name, LastModifiedDate, Rating FROM Account WHERE Name='Test Account' LIMIT 1];
        acc.Rating = 'Cold';
        Test.startTest();
        Update acc;
        Test.stopTest();
        
        Historical_Log__c hl = [SELECT Name, Account__c, Old_Rating_Value__c, New_Rating_Value__c, Time_Stamp__c FROM Historical_Log__c WHERE Account__c =:acc.Id];
        System.assertEquals(acc.Id, hl.Account__c);
        System.assertEquals('Hot', hl.Old_Rating_Value__c);
        System.assertEquals('Cold', hl.New_Rating_Value__c);
        System.assertEquals(acc.LastModifiedDate, hl.Time_Stamp__c);
    }
}
Apex