Apex Trigger Salesforce

Triggers

Apex Code that can be invoked based on a DML event.
DML Events
Insert
Update
Delete
Upsert

Trigger Types
Triggers are categorized as two types
1. Before
2. After

Hence the triggers would be invoked based on DML events as below,
Before / After Insert
Before / After Update
Before / After Delete
After Undelete

Trigger Syntax:

trigger triggerName on ObjectName (DML_Events){
//code_block
}

trigger is a key word.
DML_Events as specified above (one event or more than one).

Example scenarios when we can use triggers.
1. Block users to delete records from an object through any means (API / Manual / Dataloader / Web services ) if there is any dependent record existing.
This scenario requires a trigger to be invoked for an event – “before delete”.
ObjectName is a API name if it is custom or Object Name for standard object.

2. When a new line item is added to an opportunity, the value of the associated product’s object field to the new record. This requires a trigger to be invoked for an event – “before insert”.
ObjectName – Opportunity

Similarly we can invoke a block of apex code to be fired based on the events mentioned.

How to refer the record under the trigger context when before / after a DML event?
System has provided the trigger context variables as follows to refer those record information.

trigger.new is used to refer the records information when an insert, update event occurs.
Returns a list of the new versions of the sObject records.

trigger.old returns a list of the old versions of the sObject records.
Note that this sObject list is only available in update and delete triggers.
It means we can not refer this variable if triggers invoked due to insert operation.

trigger.newMap – A map of IDs to the new versions of the sObject records.
Note that this map is only available in before update, after insert, and after update triggers.
It means we can not refer this variable if triggers invoked due to insert operation.

trigger.oldMap – A map of IDs to the old versions of the sObject records.
Note that this map is only available in update and delete triggers.
It means we can not refer this variable if trigger invoked due to insert operation.

Size – The total number of records in a trigger invocation, both old and new.
trigger.size() will return the number of records that invoked the trigger.

few other trigger context variables below
isExecuting – Returns true if the current context for the Apex code is a trigger.

isInsert – Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API.

isUpdate – Returns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API. +ete – Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API.

isBefore – Returns true if this trigger was fired before any record was saved.

isAfter – Returns true if this trigger was fired after all records were saved.

How to use the context variable in a trigger?
Consider a scenario where we need to value the description field in the account object with text “training or education account” for any new account inserted but only if industry is “Education”.

For this case, the event should be “before inserted”. The object is “Account”.
Hence the trigger would be as follows

trigger updateEnergyAccounts on Account(before insert){

}

How to check the account record’s industry field even before it is inserted?.
This is where we need to use the trigger context variable.

trigger updateEnergyAccounts on Account(before insert){
    list<account> accList = trigger.new; 
}

Note that the trigger.new is a list of sObject of type under the trigger context.
Hence if trigger is expected to be invoked due to only one record then it can be as follows,

trigger updateEnergyAccounts on Account(before insert){
       Account> a = trigger.new[0]; 
}

But it is good to have the trigger to handle more than one record at a time. Consider if there are account insertion performed using data loader. Then the trigger can be written as follows,

trigger updateEnergyAccounts on Account(before insert){
     list<account> accList = trigger.new;
    for(account a: accList){
         if(a.industry == 'Education'){
             a.description = "training or education account"; 
         }
    } 
}

still we can reduce few lines as follows

trigger updateEnergyAccounts on Account(before insert){
      // list<account> accList = trigger.new; -> remove and can directly refer the         
     //trigger.new in the loop.
     for(account a: trigger.new){
          if(a.industry == 'Education'){
               a.description = "training or education account"; 
          }
      } 
}

Note that the an explicit DML insert need not mention in this trigger since the insert operation had already been initiated.

How to write a test class for this trigger?
Writing a test class for a trigger would be an easy task comparing writing a test class for a apex class.
First thing we should invoke this trigger through the test class as below.

Test Class:

@isTest
private class updateEnergyAccountsTest {
    @isTest
     static void myTest(){
          Account a = new Account(Name = 'sfdcmeet Training Inc', Industry = 'Education');
          try {
              insert a;
          } catch(Exception e) {
             system.debug(e.getMessage());
          } 
      }
}

When you run this test class, the trigger would be invoked as we insert the account. As we are inserting the account with industry as “Education” the “if” condition in the trigger would also be succeeded and executed.
This would meet the 100% code coverage since all the lines in the trigger would be executed through the above test class. But as a best practice, we need to verify the output as well as follows.

@isTest
private class updateEnergyAccountsTest {
      @isTest
      static void myTest(){
             integer bfrCount = [select count() from account];
             account a = new account(Name = 'sfdcmeet Training Inc', Industry = 'Education');
            try{
                insert a;
            } catch(Exception e){
                 system.debug(e.getMessage());
              }
             integer aftrCount = [select count() from account];
             system.assertEquals(aftrCount, bfrCount + 1);

             account a1 = [select id, description from account where Id = :a.Id];
             system.assertEquals(a1.Description, 'training or education account');
      }
}

Scenario for “After Insert”

When new account is inserted for type – “Customer – Direct”, the reassign the record to user “Nancy”.

Trigger Event: After Insert

Trigger Object: Account

Trigger Context Variable: trigger.new (which will have ID of the newly inserted record in this case)

Approach:
1. Get the ownerId of the inserted account.
2. Modify the ownerId with userid of the user to be assigned.
3. Update the modified accounts.

trigger reAssignDirectAccount on Account (after insert) {
      // we need the id of the user to be assigned.
      user u = [select id from user where username = 'nancy041@sfdcmeet.com'];
      // the trigger.new can be referred directly as below for ID field. This would refer the ID 
      //field in the trigger.new collection automatically in the trigger.
      list<account> accList = [select ownerid, type from account where Id IN :trigger.new];
      // the below list is to get all the records to be modified for owner id in one collection to  
      //save the number of DML so that we can avoid hitting the governor limits.
       list<account> modAccs = new list<account>();

       for(account a: accList){
           if(a.type == 'Customer - Direct'){
                a.OwnerId = u.id;
                modAccs.add(a);
            } 
        }
       if(modAccs.size()>0)
          update modAccs;
}

Try writing a test class for this trigger.

Scenario for “delete” event.
Don’t allow user to delete contact if it is related to any account.

Trigger Event: Before Delete

Trigger Object: Contact

Trigger Context Variable: trigger.Old can be referred to get the existing record info that invoke the trigger.
This means we are referring the old version of sObject record (not the new record that inserted or modified).
This case the trigger.new will be empty.

Approach:
Check if related account exists for the contacts that are in deletion.

We can refer the trigger.Old to get Id of all the contacts under deletion. The trigger has to be written to handle more than one record at a time since the deletion may be initiated through various means with more than one record.

Get all accountIds from trigger.Old.

Get Map of Id as Key and corresponding account record as value for the above accountIds.
AccountId is the foriegn key (Id of an account record) in contact object.

Now just iterate the trigger.Old and check the Map of Id, account to verify if key exists.
If key exists, we can throw error ‘Not to delete the contact’. Key is nothing but the field accountId of the contact under deletion.

trigger NotDeleteContactswithAccount on Contact (before delete) {
      set<Id> accountIDs = new set<Id>(); 

      for(contact c: trigger.Old){
           accountIDs.add(c.accountId); 
       } 
       map<id, account> accountMap = new map<id, account>([select Id, name from account  
                                                            where ID IN :accountIDs]);

       for(contact c: trigger.Old){
           if(accountMap.containsKey(c.AccountId))
                c.addError('Contact related to an account can not be deleted!'); 
            } 
       }
}

The ‘addError’ is a method of Object class to throw an error during DML operation.

Scenario for “After update”
If an Account phone no is modified, update the related contacts custom field OtherPhone__c with account’s phone

Trigger Event: after update

Trigger Object: account

Approach:

1. Access the id and account records from trigger.newMap (Id, ….)
map<id, account> modAccs = trigger.newMap;

2. Compare the phone field from trigger.new with trigger.old’s phone field
map<id, account> oldAccs = trigger.OldMap;

How to refer phone field from newMap?

map<id, account> modAccs = trigger.newMap;
set<ID> accIds = trigger.newMap.keySet();
list<account> accList = trigger.new;

for(ID acid: accIds){
     if(modAccs.get(acid).phone != oldAccs.get(acid).phone){
          for(contact c: modAccs.get(acid).contacts){
              c.otherPhone__c = modAccs.get(acid).phone;
          } 
     } 
}

Actual Code

trigger updateContactPhone on Account (after update) {
      map<id, account> modAccs = new map<id, account>([select id, phone, (select name,       
                                                                                                otherPhone from contacts) 
                                                                                               from account 
                                                                                               where id IN :trigger.new]); 
       map<id, account> oldAccs = trigger.OldMap;
       set<ID> accIds = trigger.newMap.keySet(); 

       for(ID acid: accIds){
           if(modAccs.get(acid).phone != oldAccs.get(acid).phone){ 
                 list<contact> cons = new list<contact>();
                 for(contact c: modAccs.get(acId).contacts){
                      c.otherPhone = modAccs.get(acid).phone;
                      cons.add(c); 
                  } 
                 update cons;
            } 
        } 
}
trigger updateContactPhone on Account (after update) {
      map<id, account> modAccs = new map<id, account>([select id, phone, (select name, otherPhone from contacts) 
                                                         from account 
                                                        where id IN :trigger.new]); 

       for(ID acid: trigger.newMap.keySet()){
            if(modAccs.get(acid).phone != trigger.OldMap.get(acid).phone){ 
                 list<contact> cons = new list<contact>();
                 for(contact c: modAccs.get(acId).contacts){
                      c.otherPhone = modAccs.get(acid).phone;
                      cons.add(c); 
                 } 
                update cons;
             } 
       } 
}

Test Class

@isTest
private class updateContactPhoneTest {
      static testMethod void myTest(){
             account a = new account(name = 'Taylor Corp', Phone = '123');
             insert a;
             contact c = new contact(lastName = 'Krishnan', accountId = a.Id);
             insert c;

             account a1 = [select phone from account where id = :a.id];
             a1.phone = '444';
             update a1;

             contact c1 = [select otherPhone from contact where accountId = :a1.id];
             system.assertEquals(c1.otherPhone, a1.Phone);
      }
}

Related posts

Leave a Comment