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!

1
  • Account oldAccount = accountListOld.get(myAccount.Id) == null ? new Account() : accountListOld.get(myAccount.Id). Oct 7, 2019 at 19:05

1 Answer 1

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.

5
  • 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.
    – Daniel
    Oct 7, 2019 at 20:19
  • Always, always, always separate filter from action.
    – Adrian Larson
    Oct 7, 2019 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.
    – Daniel
    Oct 8, 2019 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.
    – Daniel
    Oct 9, 2019 at 14:15
  • Take a look at my answer here and it might help you put it all together.
    – Adrian Larson
    Oct 9, 2019 at 15:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .