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