Preface – This post is part of the Object Oriented Thinking series.
Finally, a real-life example of how and why to use objects in a business scenario!
Let’s say your company runs a subscription business and uses a custom “Member” object. Instead of converting leads to the standard Contact/Account/Opportunity model, your company converts leads to members using the following Apex class:
public class SpecialLeadConvert { // VariablesList<Lead> leads; List<Member__c> members;// Constructorpublic SpecialLeadConvert(List<Lead> leadList) {leads = leadList; members = new List<Member__c>(); } // Method: convert leads to Members, not Contactspublic void convertToMembers() {for (Lead l : leads) { Member__c member = new Member__c(); member.Name = l.FirstName + ' ' + l.LastName; member.Email__c = l.Email; members.add(member); } insert members; // Delete the leads // Apex doesn't let us delete records in Trigger.new // This is a workaround that tricks Apex! List<Lead> workaround = leads.deepClone(true); delete workaround; } // Method: get our Member listpublic List<Member__c> getMembers() {return members; } }
Now here’s how we’d use this class’s logic in a trigger:
trigger ConvertToMember on Lead (before update) { // Make a list of leads to convert List<Lead> leadsToConvert = new List<Lead>(); for (Lead l : Trigger.new) { if (l.Status == 'Member') { leadsToConvert.add(l); } } // Create the special conversion objectSpecialLeadConvert slc = new SpecialLeadConvert(leadsToConvert);// Use the object's method to convert its leadsslc.convertToMembers();// Don't forget to use System.debug!System.debug('Members created: ' + slc.getMembers());}
A good question to ask at this point is… did we really need to use a class? Why separate the code in a class, when we could’ve had similar code inside the trigger all along?
It’s better to use a class because we’re likely to re-use the class’s logic in other places. For example, we might have a Visualforce page that also converts leads. If all of our conversion code was in our trigger (instead of a class), we’d need a separate copy of the code in our Visualforce page logic.
Using a class will also organize our codebase. Let’s say our business expands and leads can now also become “Partners”, an exclusive type of membership with extravagant benefits. We’d simply create a new convertToPartners method in our class, and whenever or wherever we’d need to do any type of conversion, we’d know our SpecialLeadConvert class would be able to handle it.
Hope this example gets those juices flowing inside your head!
Next post: The “One Trigger to Rule them All” design pattern!
Did you got , what the error was?
What is specialLeadConvert here . It’s showing Invalid here .Theres no such class
My bad wrong question sry.
Hi David,
Facing an error using the above code in DE org.
Error message:-
SELF_REFERENCE_FROM_TRIGGER, Object (id = 00Q7F0000022WB2) is currently in trigger ConvertToMember, therefore it cannot recursively delete itself: []: Class.SpecialLeadConvert.
In learn@sfdc.com org the same is working fine by deleting the lead.
please help me to spot the error. Thank you.
Regards,
Prabhu
Did you got , what the error was?
Hi David,
I tried to fire the trigger by updating one of my Lead’s status as ‘member’, but its failing when the delete DML op is called.
Giving the error as “Self Reference from Trigger, Therefor it cannot recursively delete itself”.
It is working if commented the delete DML stmt in the Class, Where as the lead will still be there and a new member record is created.
Also i tried to verify by comparing with your login credentials. So, from your Org, when i tried to fire the trigger, the lead is getting deleted and a new member record is created.
Then why it was not working in my Org. Would need to do any other config or need to enable/disable something here.
I’ve also seen where people pass Trigger.New and Trigger.Old as well as Trigger.newMap and Trigger.oldMap straight to the class handling the business logic. Are these both acceptable as best practices or is there a reason I should be handling some of the logic in the trigger itself before passing the values? Example…
Trigger…
opportunityTriggerHandler handler = New opportunityTriggerHandler();
if((Trigger.isInsert || Trigger.isUpdate) && Trigger.isBefore){
handler.oppNameChange(Trigger.New, Trigger.Old, Trigger.newMap, Trigger.oldMap, trigger.isBefore, trigger.isAfter, trigger.isInsert, trigger.isUpdate);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~
Handler….
public with sharing class opportunityTriggerHandler {
public void OppNameChange(List triggerNew, List triggerOld, Map triggerNewMap, Map triggerOldMap, boolean isBefore, boolean isAfter, boolean isInsert, boolean isUpdate){
//Some kind of apex here to perform some kind of action for some kind of reason
}
Totally acceptable to pass all of those =) If you’re comparing old vs new values, you definitely need to pass em both. Otherwise, just one is fine
Hi David,
Thank you so much for this post. :) Improving myself alot.
While executing this, when I am changing the status of the existing lead to “Member”, i am getting an error “””error: Invalid Data. Review all error messages below to correct your data.
Apex trigger ConvertToMember caused an unexpected exception, contact your administrator: ConvertToMember: execution of BeforeUpdate caused by: System.DmlException: Delete failed. First exception on row 0 with id 00Q9000000jSXibEAG; first error: SELF_REFERENCE_FROM_TRIGGER, Object (id = 00Q9000000jSXib) is currently in trigger ConvertToMember, therefore it cannot recursively delete itself: []: Class.SpecialLeadConvert.convertToMembers: line 19, column 1 “”””. Pls help me on this
Thanks much
Common david ….take a bow….excellent
Thanks David ..It helps a lot… :)
Great article!
What do you think about adding the real lead conversion?
Just giving an example :
/* beginning of you code*/…
public void convertToMembers() {
list leadsConvertList = new list();
LeadStatus convertStatus = [SELECT Id, MasterLabel FROM LeadStatus WHERE IsConverted=true LIMIT 1];
Database.LeadConvert tmpLead = new database.LeadConvert();
for (Lead l : leads) {
tmpLead.setLeadId(l.id);
tmpLead.setConvertedStatus(convertStatus.MasterLabel);
leadsConvertList.add(tmpLead);
Member__c member = new Member__c();
member.Name = l.FirstName + ‘ ‘ + l.LastName;
member.Email__c = l.Email;
members.add(member);
}
insert members;
//converting the leads
List results = Database.convertLead( leadConversions );
……/* end of you code*/
Yes you can certainly use the actual LeadConvert class to do something similar!
It all depends on your org’s needs. I kept that class out of this post for simplicity reasons, but there is absolutely nothing wrong with using it!
Hi David,
No doubt a great post. But one question came to my mind, normally when you will convert your lead to member, there should be some button on the lead details page and clicking on that your lead will be converted into members. If that is the case, then function in the controller should be able to do that, we don’t need any trigger right? From the example it looks like any update on the lead record will convert the lead into member. But there can be some situation where only phone number/email address is changed for a particular lead, but then as per the above code, those leads will be converted into members. What do you think so?
Regards,
Sudipta
Great question!
A class is beneficial for the exact reasons you specified – we can re-use the logic in a button, controller, Visualforce, trigger, batch Apex, or wherever!
In this case, we use the logic in a trigger (but assume it’s also used elsewhere). Also note that the lead is deleted immediately after it’s converted!
Yes i agree with you.
I just wanted to share because in a similar case i had to hide the convert buton and manageeverything with triggers. =)
Beautiful example. Reading other peoples’ code has proven so helpful. Thanks for sharing this with us.
No problem, hope your journey is going well Katie!!
Hi David,
Just so I understand, the deepclone makes an exact copy of the leads (ids included) and then we delete them as they are not protected by the trigger rules?
Thanks very much!
Lee
Exactly!
Since leads points to Trigger.new, Salesforce knows not to allow it to be deleted. By making a copy of the list (which, is actually the exact same records, ID included, just a different “pointer”), Salesforce has no idea it’s referencing Trigger.new =)