Preface – This post is part of the Write Your First Intermediate Trigger series.
View Chapter 4 quiz questions without the answers!
Chapter 4 Questions:
1. Without SOQL, what fields are available for each record entering a trigger?
2. What character must be placed before every Apex variable used in a SOQL query?
3. What does an error look like to the end user when adding an error to a record using the .addError() method?
4. Why shouldn’t a developer query for production records in a test class?
5. Why should every test class use System.assertEquals(), even if your code works 100% of the time?
6. What’s the negative test case for a trigger that divides the Amount by the number of days until the Close Date?
7. Which is the correct way of using System.assertEquals()?
System.assertEquals(‘English Bulldog’, favoriteDog);
System.assertEquals(favoriteDog, ‘Toy Poodle’);
Chapter 4 Practice Trigger
Write a trigger on Leads that checks to see if another Lead or Contact has the exact same name. If so, populate a “Potential Lead Duplicate” or a “Potential Contact Duplicate” field. Don’t forget the test class!
trigger DetectDupes on Lead (before insert, before update) { for (Lead l : Trigger.new) { if (l.FirstName != null && l.LastName != null) { List<Lead> dupeLeads = [SELECT Id FROM Lead WHERE FirstName = :l.FirstName AND LastName = :l.LastName AND Id != :l.Id]; if (dupeLeads.size() > 0) { l.Potential_Lead_Dupe__c = dupeLeads[0].Id; } else { l.Potential_Lead_Dupe__c = null; } List<Contact> dupeCts = [SELECT Id FROM Contact WHERE FirstName = :l.FirstName AND LastName = :l.LastName]; if (dupeCts.size() > 0) { l.Potential_Contact_Dupe__c = dupeCts[0].Id; } else { l.Potential_Contact_Dupe__c = null; } } } }
@isTest public class TestDetectDupes { static testMethod void testDetectDupes() { Lead lead1 = new Lead(); lead1.FirstName = 'Frodo'; lead1.LastName = 'Baggins'; lead1.Company = 'Shire'; insert lead1; Contact ctc1 = new Contact(); ctc1.FirstName = 'Frodo'; ctc1.LastName = 'Baggins'; insert ctc1; Lead dupe = new Lead(); dupe.FirstName = 'Frodo'; dupe.LastName = 'Baggins'; dupe.Company = 'Shire'; insert dupe; dupe = [SELECT Id, Potential_Lead_Dupe__c, Potential_Contact_Dupe__c FROM Lead WHERE Id = :dupe.Id]; System.assertEquals(lead1.Id, dupe.Potential_Lead_Dupe__c); System.assertEquals(ctc1.Id, dupe.Potential_Contact_Dupe__c); dupe.FirstName = 'Bilbo'; dupe.LastName = 'Baggins'; update dupe; dupe = [SELECT Id, Potential_Lead_Dupe__c, Potential_Contact_Dupe__c FROM Lead WHERE Id = :dupe.Id]; System.assertEquals(null, dupe.Potential_Lead_Dupe__c); System.assertEquals(null, dupe.Potential_Contact_Dupe__c); } }
Hi David,
Thanks for all your efforts for this training.
I would like to know if we can find the duplicate leads by comparing the Lead/Contact Full Name instead of First Name and Last Name.
Is this possible?
Thanks again. UT.
List dupeLeads = [SELECT Id FROM Lead
WHERE FirstName = :l.FirstName
AND LastName = :l.LastName
AND Id != :l.Id];
this is a” before insert “so how can we get l.id and why should we compare id s here
sorry if my doubts doesn’t make any sense
Radha,
Answer to your first question: The l.id that you see in the statement is for the record that is being updated (before update) and not for the record that is being created.
Answer to your second question: Think of a scenario where you go in to change something on an existing lead record that isn’t a duplicate. When you save it, you would want this record to be excluded from the dupeLeads List; that is why we are doing the Id != :l.Id statement.
Thank you Mayank!
but ,in the case of inserting record will it not give any error for referring l.id
It wouldn’t throw any error. For newly inserted records, the Id is considered ‘Null’ so the Id != :l.Id statement for an insert in the above SOQL query would simply mean that ‘Hey, Find me all leads that have FirstName = :l.FirstName, LastName = :l.LastName AND Id != Null.
David, please correct me if I am wrong.
thank u
In trigger you write the SOQL into the LOOP,
here no any solution for that to write SOQL out of the loop.???
Great observation! I hadn’t yet covered bulkifying so I didn’t expect it on the quiz!
Hi David,
While writing the test class for the practice trigger, I have got confused with the below line of code in you test class.
dupe = [SELECT Id, Potential_Lead_Dupe__c, Potential_Contact_Dupe__c FROM Lead WHERE Id = :dupe.Id];
here dupe supposed to be an List .. But it’s an instance of Lead Object . Its confusing. Please correct me.
Hey Devi,
We can use the dupe instance of the object to store the result of SOQL query in the above test class because a test class always run on a fresh clean database and since the query is only returning one row , we are allowed to store it in the dupe variable. However, if the SOQL query were to return multiple rows of data, we would have had to use a List variable to store the result.
If you want, you can use a List variable in the above test class and the code will run just fine.
David
Are you not breaking the basic principles of a Trigger, No SOQL inside a FOR Loop here ?
Great observation! The only reason I did so in this quiz is because up to Chapter 4, Governor Limits haven’t been taught yet. But in a production org, yes, no SOQL in a loop!!
I wanted to ping your brain here, would it be a cardinal sin, if I used the for loop : Trigger new — 2 times in my trigger?
Once to collect the values and store it in a SET or LIST then run the SOQL
2nd Time to compare it against the Trigger.new values and do a addError against it ?
I do it all the time, nothing wrong with it!
trigger.new is already a list of records then why to collect the values and store it in a record?
Hi David,
My trigger has no errors .
However, the fields are not populated. But why is it so?
trigger dupetrg on Lead (before insert,before update) {
list la= new list();
list ca=new list();
for(lead lb : trigger.new)
{
for(lead l3: la)
{
if(l3.Title==lb.Title)
l3.dupe__c= ‘checked’;
}
for(contact c3: ca)
{
if(c3.Title==lb.Title)
c3.Languages__c= ‘checked’;
}
}
}
Thanks in advance
Try using System.debug to see where things break down:
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
I recommend making your variables more descriptive too – they are very confusing to someone who is looking at your code for the first time!
Hi David,
I tried to implement the code. But the Field Potential_Lead_Duplicate__c, is not getting populated. I created the field with Text Type. Field Type should be Okie? Tried to show up an message( l.addError(‘Update’); ) in the If part. The message is shoowing up, but not with the field update. No Validation Rule in my system for Lead. Please Help me.
You definitely need to read this post:
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
It’ll solve a lot of the issues you’re coming across, promise!
Hi David
This is great! Learning about triggers!
The only bit I am a bit confused about is the test class where we have
dupe = [SELECT Id, Potential_Lead_Dupe__c, Potential_Lead_Contact__c
FROM Lead
WHERE Id = :dupe.Id];
How does the test class know about the Potential_Lead_Dupe__c custom field and also the Potential_Custom_Dupe__c custom field?
Does it read the assignment in the trigger where
if(dupeLeads.size() >0) {
l.Potential_Lead_Dupe__c = dupeLeads[0].Id;
} //and so on…
My guess is when you name the test class TestDetectDupes it then know to fire the DetectDupes trigger and then it all falls into place?
Thanks for all your help!
Tony
Hi David,
I am getting the error while testing the trigger-
System.AssertException: Assertion Failed: Expected: 00Q9000000UrwN0EAJ, Actual: null
Class.TestDetectDupes.testDetectDupes: line 27, column 1
Error is giving every time a new record id.
Make sure you don’t have any validation rules that are preventing the records from being created!
Fresh Org.No validation rule.Same error
Double checked in my org and it’s working!
I recommend System.debug!
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
David, I modified you trigger to the below code, and am not getting why the code coverage doesnot cover the error message alert: How to go about with this:
trigger CheckLeadDuplication on Lead (before insert,before update) {
for(lead l : Trigger.new)
{
list l1 = [select id,name from lead where name =: l.Name];
if(l1.size() >0)
{
l.adderror(‘Duplicate Lead found for’+ l.id);
}
}
Most likely you haven’t created any leads in your test class, which means some of the lines won’t run (because l1.size() is zero)!
Can you please explain me this statement
l.Potential_Lead_Dupe__c = dupeLeads[0].Id;
We’re setting the lead’s “Potential Lead Dupe” lookup field to the first record in our dupeLeads list. Lookup fields are always populated using IDs so we’re getting the ID of our first record in the dupeLeads list.
David,
just to get a clear idea what I have understood from tha last question is that if names matches you are creating a field called potential_duplicate_leads right?
because I tried to do it with custom Sobject and getting errors
saying that the field or variable does not exist…
I even tried to just paste the same code from here…same error pops up saying ” Compile Error: Invalid field Potential_Lead_Dupe__c for SObject Lead at line 9 column 9″
The field already exists actually (Apex almost never creates new fields!) and we’re just populating it =)
DY, my question is – since the trigger runs every time an update (or insert) is attempted on Lead, will having the line – update myLead; – within your trigger not cause the whole trigger to fire again?
What I am saying is, I think your logic is sound if you take out lines 8 and 12, because I believe they will cause your trigger to fire repetitively non-stop. Do you get it?
Hi David,
Great Exercise again. Hats off for your work and dedication !!
In the test class when second duplicate Lead instance ‘dupe’ is created than the trigger would have already updated the Potential_Lead_Dupe__c & Potential_Contact_Dupe__c fields for dupe instance with the proper Id, So why there is a need to explicitly assign the field value to ‘dupe’ instance through SOQL query ?
Why we can’t straight away use dupe.Potential_Lead_Dupe__c & dupe.Potential_Contact_Dupe__c for assertion ?
Even though the changes were saved and made in the database, we won’t have all the latest values available in the code. That’s why we query first to get them!
The only exception to this is IDs – they become available to you right away in your code without need a SOQL query!
(You can test all this out by removing the SOQL and testing!)
Thank you very much for clarifying this doubt David :)
Hey David.. :)
the answer to question 2 has a typo.. It is a colon and not a semi colon.
GREAT CATCH!!! Updated! Thank you!
HI David,
Why there is a need to again create the instance of lead, as we have already created lead1. ~We could have updated the lead1 record??
Great question!
We need two Leads because we check for any duplicate Leads and Contacts!
So we need one Lead as our original, one Lead to be the Lead version of the duplicate, and one Contact to be the Contact version of the duplicate!
Since your query is inside the for loop, it could run into governor limits with mass processing, right? If that’s the case, would the be a way to bulkify it? I’m not quite sure if this would work, especially with the two consecutive for loops nested within the other one.
trigger IdentifyDupes on Lead (before insert, before update) {
List allLeads = [SELECT Id, FirstName, LastName FROM Lead] ;
List allContacts = [SELECT Id, FirstName, LastName FROM Contact] ;
for (Lead myLead : Trigger.new) {
For(Lead l : allLeads) {
If(l.FirstName == myLead.FirstName && l.LastName == myLead.LastName && l.id != myLead.id) {
myLead.Potential_Lead_Duplicate__c = l.id;
Update myLead;}
For(Contact c: allContacts) {
If(c.FirstName == myLead.fullName && c.LastName == mtLead.LastName) {
myLead.Potential_Contact_Duplicate__c = c.id;
Update myLead;}
}
}
Excellent observation!! You are ready for Chapter 5 =)
(The entire chapter is dedicated to showing you how to bulkify!)
Make sure to try the quiz to make sure you’re learning correctly!
David
Hello DY,
I like the logic you applied in your trigger, and thanks for sharing it here. I noticed though that you have a line of code updating Lead (update myLead;) from within your trigger – which is itself a ‘before update’ trigger. It appears to me that the ‘update myLead’ line will trigger your trigger all over again, resulting in an infinite back and forth cycle of trigger calls. What do you think?
It’s ok to have it in the test class (since that’s independent from the trigger) but you are right if it was in the trigger we’d have a major problem!
(Let me know if I’m not seeing the update call that you’re referring to!)
Hi David,
I was referring to lines 8 and 12 in DY’s version of the trigger. I numbered the lines so it’s easier to reference.
Thanks.
1. trigger IdentifyDupes on Lead (before insert, before update) {
2. List allLeads = [SELECT Id, FirstName, LastName FROM Lead] ;
3. List allContacts = [SELECT Id, FirstName, LastName FROM Contact] ;
4. for (Lead myLead : Trigger.new) {
5. For(Lead l : allLeads) {
6. If(l.FirstName == myLead.FirstName && l.LastName == myLead.LastName && l.id != myLead.id) {
7. myLead.Potential_Lead_Duplicate__c = l.id;
8. Update myLead;}
9. For(Contact c: allContacts) {
10 If(c.FirstName == myLead.fullName && c.LastName == mtLead.LastName) {
11 myLead.Potential_Contact_Duplicate__c = c.id;
12. Update myLead;}
13. }
14. }
Hmm, now that I’m looking at it again, I would probably add the duplicate lead to a list that stores leads that need to be updated, and then make one call at the end to update the entire list. Not sure if that gets at the issue that you’re pointing out, though.
You are right, great call!!
Since there’s already an “update” (implied by the before update), you don’t need to have the line to update any leads =) It will do so automatically!
David