2

Sample Trigger:

trigger AccTrigger2 on Account (before insert) {

    //Condition I am adding and this should run before the other condition.
    for(Account accObj: Trigger.new){
        if(accObj.Name.equalsIgnoreCase('Test')){
            accObj.addError('Name cannot be test');
            return;
        }
    }

    //Condition already present in the system
    for(Account accObj: Trigger.new){
        if(accObj.BillingState.equalsIgnoreCase('MA')){
            accObj.addError('Please select different state');
        }       
    }

}

Below records are coming into the trigger:

  1. Name = 'Test', BillingState = 'CA'
  2. Name = 'Test', BillingState = 'MA'
  3. Name = 'Acme', BillingState = 'MA'
  4. Name = 'Acme', BillingState = 'CA'

Output I am trying achieve:

enter image description here

Output 1(with return statement in the trigger):

enter image description here

Output 2(with return statement commented):

enter image description here

Question: Is there a way to update the trigger so that I can get the output I need. I cannot update the two conditions as one logic as the logic for each condition is very complex and we don't want to touch the existing logic.

3
  • What do you mean get the output you need? Both validations work as expected, no? – Adrian Larson May 23 '17 at 22:01
  • In the output 2 for the record 2, I want the error to be "Name cannot be test". I am not able to get this without adding the return statement. If I add return statement I am getting error on record 4(output 1) – javanoob May 23 '17 at 22:03
  • 1
    @javanoob By the way, use == instead of equalsIgnoreCase. They do the same thing, but the latter throws an exception if the field is null... – sfdcfox May 24 '17 at 0:13
2

You could use a map and use the sObjects themselves as keys, eg:

Map<Account,String[]> accountErrors = new Map<Account,String[]>();

for(Account accObj: Trigger.new){
    if(accObj.Name.equalsIgnoreCase('Test')){
        if (!accountErrors.containsKey(accObj)){
          accountErrors.put(accObj,new String[]{'Name cannot be test'});
        }
        else {
          accountErrors.put(accObj,accountErrors.get(accObj).add('Name cannot be test'));
        }
    }
}

//Condition already present in the system
for(Account accObj: Trigger.new){
    if(accObj.BillingState.equalsIgnoreCase('MA')){
        if (!accountErrors.containsKey(accObj)){
          accountErrors.put(accObj,new String[]{'Please select different state'});
        }
        else {
          accountErrors.put(accObj,accountErrors.get(accObj).add('Please select different state'));
        }
    }       
}

//finally loop the errors map:
for (Account acct : accountErrors.keySet()){
  String[] errors = accountErrors.get(acct);
  for (String err : errors){
    acct.addError(err);
  }
}

You can extract the addition of errors to the map like so:

Map<Account,String[]> addCustomErrors(Map<Account,String[] accountErrors, Account accObj, String error){
    if (!accountErrors.containsKey(accObj)){
      accountErrors.put(accObj,new String[]{error});
    }
    else {
      accountErrors.put(accObj,accountErrors.get(accObj).add(error));
    }
    return accountErrors;

}

So inside your existing code you would only need to change:

accObj.addError('Name cannot be test');

To:

accountErrors = addCustomErrors(accountErrors,accObj,'Name cannot be test');
8
  • Looks like there is no easy way than rewriting the whole logic. – javanoob May 23 '17 at 23:33
  • Well, look at my addition - you can just swap out one line for the other and it won't disrupt your code too much. And take note of what @Eric is saying - if all (or almost all) your records are failing, then you will continue to get failures. – Caspar Harmer May 23 '17 at 23:42
  • This is awesome! creating a customAddError method seems easy then rewriting the whole logic. – javanoob May 23 '17 at 23:48
  • Oh great. Glad it worked for you. – Caspar Harmer May 23 '17 at 23:49
  • 1
    @javanoob Oh. I misread the code. You should be adding the errors to each field. Top-level errors should be reserved for situations where you can't identify the field, such as accObj.Name.addError('Name cannot be test.'); – sfdcfox May 24 '17 at 0:23
1

If you want to control order of precedence of trigger validations, you would need to adopt a pattern where you validate the collection, and then apply all validations on a record by record basis. What I mean by that is:

public with sharing class AccountService
{
    public static void validate(List<Account> records)
    {
        for (Account record : records) validate(record);
    }
    static void validate(Account record)
    {
        if (/*Condition 1*/)
        {
            record.addError('Message 1');
            return;
        }
        if (/*Condition 2*/)
        {
            record.addError('Message 2');
        }
    }
}
1
  • I agree, I should rewrite the trigger like as you suggested but the existing code is too complex and I was looking for shortcut. I know shortcuts will break in the future or cause headache for future developers working on this code :) – javanoob May 24 '17 at 0:01

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.