Simplifying field change criteria on Apex Triggers

Posted on October 10th, 2014 at 11:32 am


Defining field criteria for Apex Triggers can be quite cumbersome. For instance, you might have noticed when creating a Salesforce workflows, you can use a function called ISCHANGED(field). Since we don't have any built-in functions for triggers, I decided to create my own.

Take a look at the following example. We want to update a list of accounts, but only when the fields Name, AnnualRevenue, Industry, or Description are updated. Here's how you would do this without my helper methods:

//Create a list for storing accounts with changes
List<Account> accountWithFieldChangesList = new List<Account>();

//Create a set to prevent duplicate accounts from being count
Set<Account> accountWithFieldChangesSet = new Set<Account>();

//Loop through all accounts in the Trigger
for(Account act : Trigger.new){

    //Identify the original record
    Account oldAct = Trigger.oldmap.get(act.Id);

    //If the Name field has changed, add it to the set
    if(act.Name != oldAct.Name){
        accountWithFieldChangesSet.add(act);
    }

    //Continue with AnnualRevenue
    if(act.AnnualRevenue != oldAct.AnnualRevenue){
        accountWithFieldChangesSet.add(act);
    }

    //Etc.
    if(act.Industry != oldAct.Industry){
        accountWithFieldChangesSet.add(act);
    }

    //Etc.
    if(act.Description != oldAct.Description){
        accountWithFieldChangesSet.add(act);
    }
}

//Now check if the set is empty
if(!accountWithFieldChangesSet.isEmpty()){

    //Add the set values to the list
    accountWithFieldChangesList.addAll(accountWithFieldChangesSet);

    //Finally update our list
    update accountWithFieldChangesList;
}

Not only does this hard to read, but it doesn't follow the DRY (Do not repeat yourself) principle. And what happens when we add more complexity? This can become a big mess really quickly.

Solution

My solution is to create a helper class I called: HelperTriggerUtility. This class creates an object that require a new and old sObject record in it's constructor and then allows you to check if a single field has changed with the hasFieldChanged(fieldname) method. If you need to check multiple fields, you can use the hasAnyFieldChanged(List<String> fieldnames) method.

Here's the code:

public with sharing class HelperTriggerUtility {

    private SObject newObj;
    private SObject oldObj;

    public HelperTriggerUtility(SObject oldObj, SObject newObj) {
        this.newObj = newObj;
        this.oldObj = oldObj;
    }

    public Boolean hasFieldChanged(String fieldname)
    {
        if(oldObj.get(fieldname) != newObj.get(fieldname)){
            return true;
        } else {
            return false;
        }
    }

    public Boolean hasAnyFieldChanged(List<String> fieldnames)
    {
        for(String field : fieldnames){
            if(oldObj.get(field) != newObj.get(field)){
                return true;
            }
        }

        return false;
    }
}

Take a look at my gist for the included unit tests.

Implementation

Let's see how this works:

//Create a list for storing accounts with changes
List<Account> accountWithFieldChangesList = new List<Account>();

//Loop through all accounts in the Trigger
for(Account newAct : trigger.new){

    //Identify the original record
    Account oldAct = Trigger.oldmap.get(newAct.Id);

    //Initialize the helper object and inject the old and new account record
    HelperTriggerUtility helper = new HelperTriggerUtility(oldAct, newAct);

    //Create a list of fields that we want to check.
    List<String> fieldList = new List<String>{'Name','AnnualRevenue','Industry','Description'};

    //Run the helper method
    Boolean pass = helper.hasAnyFieldChanged(fieldList);

    //Add to list if account passes
    if(pass){
        accountWithFieldChangesList.add(newAct);
    }

}

//Check if the list is empty
if(!accountWithFieldChangesSet.isEmpty()){

    //Update our list
    update accountWithFieldChangesList;
}

That's all, hope it makes your life easier!

comments powered by Disqus