The trigger logic worked as we expected! What if we try to delete the record?
Unexpected error when deleting the opportunity
How’s that?
This is related to how the trigger is written. Let’s jump back to the Developer Console and the Logs panel:
Debugging log after an unexpected record on a save operation
- Double-click on the row whose Status shows Attempt to de-reference a null object (one of the ugliest errors a developer can face, not because it is not recoverable but because it means that they haven’t designed their code in the best way). Scroll down on the Execution Log or use the Filter input box and write FATAL_ERROR:
Filtering a debug log
This is called an unexpected exception: exceptions are a way that Apex uses to tell the developer that something went wrong, and the code failed to execute as expected.
The problem is related to OpportunityTrigger on line 5, where the currentOpp variable is being set. So, what’s the problem?
We are doing a delete operation and the trigger is set up to support it (its definition supports the before delete and after delete events), but the delete operation does not contain any new value, so if we actually had to handle a delete operation we should have used the Trigger.old property instead (like we did for the old values on the if statement of Trigger.isUpdate in the Trigger.isBefore section).
We have two options:
- Move the currentOpp variable assignment inside a statement that explicitly refers to insert and update operations.
- Simply edit the trigger not to support delete (and undelete) operations.
We’ll go with the second way, as we are not actually supporting delete/undelete operations. If for some reason in the future we want to support these events, we’ll go through trigger refactoring to support the new DML operations.
The trigger declaration becomes simpler:
1. trigger OpportunityTrigger on Opportunity (before insert,
2. before update, after insert, after update) {
3. . . .
From now on, no other exception will be thrown.
We said that in before triggers we can even change the record fields without executing a new save operation (a new DML), such as filling in default values using Apex code.
Let’s create a new custom field on the opportunity object, a checkbox that is used to tell whether the opportunity is the first opportunity ever created for its account in the current month:
New custom field on the Opportunity object
Let’s add some logic for the before insert event after the amount validations:
1. trigger OpportunityTrigger on Opportunity (before insert, before
2. update, after insert, after update) {
3.
4. //current record (supposing only one opportunity can be saved at
once)
5. Opportunity currentOpp = Trigger.new[0];
6.
7. //treating before and after events separately
8. if(Trigger.isBefore){
9.
10. //following logic 0runs in before insert and update
11. if(Trigger.isInsert || Trigger.isUpdate){
12. // . . .
13. }
14.
15. //folowing login runs only for before update
16. if(Trigger.isUpdate){
17. //. . .
20. }
21.
22. //following logic runs in before insert only
23. if(Trigger.isInsert){
24.
25. //checks if current opportunity is the first opp.
26. //for the current month for its account
27. if(currentOpp.AccountId != null){
28. Integer monthOppties = [Select count()
29. From Opportunity
30. Where AccountId =
:currentOpp.AccountId
31. and CloseDate = THIS_MONTH];
32. currentOpp.First_Mounty_Opp__c = monthOppties == 0;
33. }
34.
35. }
36. }else if(Trigger.isAfter){
37. if(Trigger.isUpdate || Trigger.isInsert){
38. //after logic goes here…
39. }
40. }
41. }
This piece of code must run on the before event and only for insert operations (that’s why there’s the new if statement of Trigger.isInsert); then we check whether the AccountId field is filled in (otherwise, the opportunity is not related to any account and our business requirement makes no sense).
monthOppties is an integer variable that contains the result of the SOQL query used to get the number (the count() SOQL function) of opportunities that are related to the same account as the current opportunity, whose created date is in this month (THIS_MONTH is a SOQL constant that can be used when referring to date/time filters).
The new Opportunity.First_Month_Opp__c field is then flagged only if the total number of opportunities is 0.
As you can see, after this field assignment no insert/update statement is executed (we’ve seen how to insert a new record in Chapter 4, Extending Custom Objects, using the update Apex keyword). This is because it is not needed, as we are in the before event, and so any modifications to the fields are propagated in the save operation.
If you try to create a new opportunity with a new account that hasn’t got any other opportunities for the current month, the new checkbox is flagged and if you try to create a new one, it doesn’t.
Try setting other fields yourself and see what happens after the record is saved.
To finish this complex example, let’s complete the after event logic.
The requirement is to set a new field on the Account object that stores the total number of deals that are closing in the current month.
The first thing we do is create a new field on the account object:
New field on the Account object to store all closing deals