Wednesday, 25 January 2012

Controlling Recursive Triggers


Controlling Recursive Triggers
You want to write a trigger that creates a new record as part of its processing logic; however, that record may then cause another trigger to fire, which in turn causes another to fire, and so on. You don't know how to stop that recursion.
Solution
Use a static variable in an Apex class to avoid an infinite loop. Static variables are local to the context of a Web request (or test method during a call to runTests()), so all triggers that fire as a result of a user's action have access to it.
For example, consider the following scenario: frequently a Salesforce.com user wants to follow up with a customer the day after logging a call with that customer. Because this is such a common use case, you want to provide your users with a helpful checkbox on a task that allows them to automatically create a follow-up task scheduled for the next day.
You can use a before insert trigger on Task to insert the follow-up task, but this, in turn, refires the before insert trigger before the follow-up task is inserted. To exit out of this recursion, set a static class boolean variable during the first pass through the trigger to inform the second trigger that it should not insert another follow-up task:
Note
http://www.salesforce.com/docs/developer/cookbook/Content/images/help/helpNote_icon.gifFor this Apex script to work properly, you first must define a custom checkbox field on Task. In this example, this field is named Create_Follow_Up_Task__c.
The following code defines the class with the static class variable:
public class FollowUpTaskHelper {

    // Static variables are local to the context of a Web request 
   
    // (or testMethod during a runTests call) 
   
    // Therefore, this variable will be initialized as false 
   
    // at the beginning of each Web request which accesses it. 
   

    private static boolean alreadyCreatedTasks = false;


    public static boolean hasAlreadyCreatedFollowUpTasks() {
        return alreadyCreatedTasks;
    }

    // By setting the variable to true, it maintains this 
   
    // new value throughout the duration of the request 
   
    // (or testMethod) 
   
    public static void setAlreadyCreatedFollowUpTasks() {
        alreadyCreatedTasks = true;
    }


    public static String getFollowUpSubject(String subject) {
        return 'Follow Up: ' + subject;
    }

}
The following code defines the trigger:
trigger AutoCreateFollowUpTasks on Task (before insert) {

    // Before cloning and inserting the follow-up tasks, 
   
    // make sure the current trigger context isn't operating 
   
    // on a set of cloned follow-up tasks. 
   
    if (!FollowUpTaskHelper.hasAlreadyCreatedFollowUpTasks()) {

        List<Task> followUpTasks = new List<Task>();

        for (Task t : Trigger.new) {
            if (t.Create_Follow_Up_Task__c) {

                // False indicates that the ID should NOT 
   
                // be preserved 
   
                Task followUpTask = t.clone(false);
                System.assertEquals(null, followUpTask.id);

                followUpTask.subject =
                FollowUpTaskHelper.getFollowUpSubect(followUpTask.subject);
                if (followUpTask.ActivityDate != null) {
                    followUpTask.ActivityDate =
                      followUpTask.ActivityDate + 1; //The day after 
   
                }
                followUpTasks.add(followUpTask);
            }
        }
        FollowUpTaskHelper.setAlreadyCreatedFollowUpTasks();
        insert followUpTasks;
    }
}
The following code defines the test methods:
// This class includes the test methods for the 
   
// AutoCreateFollowUpTasks trigger. 
   

public class FollowUpTaskTester {
    private static integer NUMBER_TO_CREATE = 4;
    private static String UNIQUE_SUBJECT =
                                    'Testing follow-up tasks';

    static testMethod void testCreateFollowUpTasks() {
        List<Task> tasksToCreate = new List<Task>();
        for (Integer i = 0; i < NUMBER_TO_CREATE; i++) {
            Task newTask = new Task(subject = UNIQUE_SUBJECT,
                    ActivityDate = System.today(),
                    Create_Follow_Up_Task__c = true );
            System.assert(newTask.Create_Follow_Up_Task__c);
            tasksToCreate.add(newTask);
        }

        insert tasksToCreate;
        System.assertEquals(NUMBER_TO_CREATE,
                            [select count()
                             from Task
                             where subject = :UNIQUE_SUBJECT
                             and ActivityDate = :System.today()]);

        // Make sure there are follow-up tasks created 
   
        System.assertEquals(NUMBER_TO_CREATE,
           [select count()
            from Task
            where subject =
           :FollowUpTaskHelper.getFollowUpSubject(UNIQUE_SUBJECT)
           and ActivityDate = :System.today()+1]);
    }

    static testMethod void assertNormalTasksArentFollowedUp() {
        List<Task> tasksToCreate = new List<Task>();
        for (integer i = 0; i < NUMBER_TO_CREATE; i++) {
            Task newTask = new Task(subject=UNIQUE_SUBJECT,
                                  ActivityDate = System.today(),
                                  Create_Follow_Up_Task__c = false);
            tasksToCreate.add(newTask);
        }

        insert tasksToCreate;
        System.assertEquals(NUMBER_TO_CREATE,
                            [select count()
                            from Task
                            where subject=:UNIQUE_SUBJECT
                            and ActivityDate =:System.today()]);

        // There should be no follow-up tasks created 
   
        System.assertEquals(0,
              [select count()
               from Task
               where subject=
               :FollowUpTaskHelper.getFollowUpSubject(UNIQUE_SUBJECT)
               and ActivityDate =:(System.today() +1)]);
    }

}
See Also


No comments:

Post a Comment

Get the Developer Name for Record Types without SOQL query

Hi, Previously, the developer name was accessible only via SOQL on the RecordType SObject, and not via describe information. Now you can ...