0

I have a general question about comparing newMap values with oldMap values in an Apex Class made for both update and insert contexts.

Let's say we have an Account Handler method that must run when an Account Name is changed or the Account is created.

When testing the below code for an update context everything will work fine since we're able to compare Trigger.oldMap with Trigger.newMap.

public class exampleAccountHandler {

public static void exampleAccountMethod (Map<Id, Account> accountListNew, Map<Id, Account> accountListOld) {




        // Code





        for (Account myAccount : accountListNew.values()) {

            if (accountListNew.get(myAccount.Id).Name != accountListOld.get(myAccount.Id).Name) {

                 // Logic

            }
        }
    }
}

In the Account Master Trigger it will look somewhat like this:

if (Trigger.isBefore)

if (Trigger.isUpdate) {

    exampleAccountHandler.exampleAccountMethod(Trigger.oldMap, Trigger.newMap);

}

When we're testing the same for an insert context it will throw a null pointer exception error since we don't have anything to compare on the oldMap side (it will run the if clause where the oldMap is referenced). Also, Trigger.oldMap cannot be used in the insert context.

What are the best practices to circumvent this issue? Define a separate method for the insert context (I assume there can only be Trigger.newMap as an input)? Adding logic to the Trigger (I guess no)? Something in between?

Looking forward to your help!

  • Account oldAccount = accountListOld.get(myAccount.Id) == null ? new Account() : accountListOld.get(myAccount.Id). – Drew Kennedy Oct 7 '19 at 19:05
2

This example highlights a common programming anti-pattern. The only reason you are having difficulty here is that you have conflated your filter and your action. You should instead have two methods here: one which filters only records whose name has changed, and one which manipulates all records on which you should act. Basically:

public with sharing class AccountService
{
    public static List<Account> hasNameChanged(List<Account> newRecords, Map<Id, Account> oldMap)
    {
        List<Account> output = new List<Account>();
        for (Account record : newRecords)
        {
            if (record.Name != oldMap.get(record.Id).Name)
            {
                output.add(record);
            }
        }
        return output;
    }
    public static void doStuff(List<Account> records)
    {
        // put your ACTION here
        // only pass in records on which you should act
    }
}

Then in your trigger handler, you should have some code like:

public void beforeInsert()
{
    AccountService.doStuff(newRecords);
}
public void beforeUpdate()
{
    AccountService.doStuff(AccountService.hasNameChanged(newRecords, oldMap));
}

In addition to simplifying your trigger handler, this pattern greatly simplifies unit testing. Now, when testing your action, data setup is much simpler. You can pass in any collection of records and they should be treated the same. Also, when verifying if your filter works correctly, you can simply check how many records you get back. This pattern should be used for every trigger action and filter, regardless of what events it fires on.

  • Thanks so much for your advice, Adrian - will give it a shot. Would you recommend the same approach (the additional filter method) for a scenario where I only have to update records? In other words: Would the code above be okay for update only contexts? If no, should I consider to generally ban if statements from record for loops in handlers? I actually see this very often. – David Oct 7 '19 at 20:19
  • Always, always, always separate filter from action. – Adrian Larson Oct 7 '19 at 20:45
  • Thank you, Adrian. As an Apex beginner I'd really like to dig a bit deeper into this topic. Can you recommend some documentation or helpful links which cover the above topic or which contain examples for this recommended pattern? Any must read? I started googling it but I wasn't able to find the 'magic keyword'. I'm already used to the 'usual suspects' of best practices but never took if statements in for each loops into consideration. – David Oct 8 '19 at 13:56
  • I need some help with calling the methods public void beforeInsert() and public void beforeUpdate() in the master trigger. Could you be so kind as to include the recommended master trigger code in your reply as well? This would really help me. – David Oct 9 '19 at 14:15
  • Take a look at my answer here and it might help you put it all together. – Adrian Larson Oct 9 '19 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.