2

Could someone help me with this basic trigger? When a new task is inserted, I want to update the Status field in the lead to match the CallResult field in the Task. Here is my broken code:

trigger callResult on Task (after insert) 
{
     for (Task task : Trigger.new)
     {
           if (task.Who != null)
           {
                String leadName = task.Who.Id;
                String callResult = task.CallDisposition;

                List<Lead> relatedLead = [SELECT Id, Status FROM Lead WHERE Name = :leadName LIMIT 1];

                if (relatedLead.size() > 0)
                {
                    relatedLead[0].Status = callResult;                  
                }

           }
     }
7
  • Don't use a trigger for this, use workflow.
    – Adrian Larson
    Jun 30, 2015 at 21:31
  • Also in Apex you can just use Task.WhoId, which is more clear than Task.Who.Id and is the actual field, not a reference object.
    – Adrian Larson
    Jun 30, 2015 at 21:33
  • Well I want the field update to occur on the Lead when the new Task is saved. Is that possible in a workflow? Jun 30, 2015 at 21:39
  • It only lets me update fields that are within the workflow's object. So if the Task is the workflow object, I'm not able to update a lead field. Jun 30, 2015 at 21:51
  • Yeah I thought at first glance you were just updating another field on Task.
    – Adrian Larson
    Jun 30, 2015 at 21:54

2 Answers 2

2

Here is a shorter (untested) solution:

trigger OnTaskInsertUpdateLeadStatus on Task (after insert) {
    Map<Id, Lead> m = new Map<Id, Lead>();
    for (Task t : Trigger.new) {
         if (t.WhoId != null && t.WhoId.getSObjectType() == Lead.SObjectType) {
             m.put(t.WhoId, new Lead(Id = t.WhoId, Status = t.CallDisposition));
         }
    }
    update m.values();
}

with some opinions:

  • The signature of the trigger - the after insert - determines when the trigger is called so no need to double check in the body of the trigger.
  • It's unlikely (but possible when a bulk update is done) that multiple Tasks related to the same Lead are handled in the trigger so for safety the map ensures only one reference to the Lead in the update.
  • Id values know the type of object they reference so you can stick to a strongly type SObjectType comparison rather than using describe calls and string prefix comparisons.
  • Only handle exceptions when you have a clear reason to do so: most exception handling code is counter productive.
  • It's been may API versions since updating an empty list counted against governor limits; empty list guards are not needed.
  • Move logic out of a trigger into a separate class when you have reason to do so. The benefits must outweigh the reduced cohesion.
3
  • By saying "The signature of the trigger - the after insert - determines when the trigger is called so no need to double check in the body of the trigger" you are essentially encouraging many triggers per object.
    – Adrian Larson
    Jun 30, 2015 at 23:56
  • I like the use of getSObjectType though and I even had that in my head as I started typing. Not sure why I left it off.
    – Adrian Larson
    Jun 30, 2015 at 23:56
  • @AdrianLarson On the first point, all I was looking at is the code you posted which did not have "before insert, before update, ..." in its signature. If it did and you included a comment why you were checking in the body then fair enough. Incidentally, I suspect the simplistic moving of code into classes results in the same sort of duplication of queries and logic that the "one trigger per type" pattern is meant to help avoid.
    – Keith C
    Jul 1, 2015 at 0:04
3

You need to perform a DML operation in order to update the leads. You should move this logic out into an Apex Class. Note below that you do not need to query for records to update if you already have their Id.

public static final String LEAD_PREFIX = Lead.sObjectType().getDescribe().getKeyPrefix();

public static void updateLeads(List<Task> tasks)
{
    List<Lead> leads = new List<Lead>();
    for (Task task : tasks)
    {
        if (isLeadId(task.WhoId))
            leads.add(new Lead(Id=task.WhoId, Status=task.CallDisposition));
    }
    if (leads.isEmpty()) return;

    try
    {
        update leads;
    }
    catch (DmlException dmx)
    {
        // map errors back to tasks
        // exercise left to reader
    }
}
public static Boolean isLeadId(Id candidate)
{
    return String.isNotBlank(candidate) &&
        String.valueOf(candidate).startsWith(LEAD_PREFIX);
}

From your Apex Trigger, calling the above is fairly simple. Let's say you called your class TaskServices.

trigger callResult on Task (after insert)
{
    if (trigger.isAfter && trigger.isInsert)
    {
        TaskServices.updateLeads(trigger.new);
    }
}
4
  • Wow! Thank you. Does this code create a new lead though? We only want this to update an existing record that it is related to. Jun 30, 2015 at 22:01
  • You can only create a new record with the insert or upsert commands. You will never get a new result based on an update command.
    – Adrian Larson
    Jun 30, 2015 at 22:02
  • Okay thank you, could you explain to me this line in the apex code: leads.add(new Lead(Id=task.WhoId, Status=task.CallDisposition)); Jun 30, 2015 at 22:05
  • It is instantiating a Lead record. We need the Id in order to update it, and we know this corresponds to the task.WhoId. Based on your example, we know that the Status should correspond to the task.CallDisposition, and this is the only change being made to the record.
    – Adrian Larson
    Jun 30, 2015 at 22:08

You must log in to answer this question.

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