The before event

Where is the record that is being saved?

The Trigger keyword comes in handy as well and gives us access to current record values (and even to old ones):

 1. trigger OpportunityTrigger on Opportunity (before insert, before  
2. update, before delete, after insert, after update,
3. after delete, after undelete) {
4.
5. //current record (supposing only one opportunity can
be saved at once)
6. Opportunity currentOpp = Trigger.new[0];
7.
8. //treating before and after events separately
9. if(Trigger.isBefore){
10.
11. //following logic 0runs in before insert and update
12. if(Trigger.isInsert || Trigger.isUpdate){
13.
14. //error if opportunity has a negative amount
15. if(currentOpp.Amount <= 0){
16. //the addError() method is used to break the save
process
17. //and is shown to the current user on the page layout
18. currentOpp.addError('Invalid negative amount on
opportunity.');
19. }
20. }
21.
22. //folowing login runs only for before update
23. if(Trigger.isUpdate){
24.
25. //gets the old values for an opportunity
26. Opportunity oldOpp = Trigger.old[0];
27.
28. //error if opportunity amount is discounted of more than
50%
29. Double newAmount = currentOpp.Amount;
30. Double oldAmount = oldOpp.Amount;
31.
32. if(newAmount > 0 && oldAmount > 0
33. && newAmount < (oldAmount * 0.5) ){
34. currentOpp.addError('Discount policies
forbit discounts '
35. + 'higher than 50%. Please review the deal. '
36. + newAmount + ' vs '+oldAmount);
37. }
38. }
39. }else if(Trigger.isAfter){
40. if(Trigger.isUpdate || Trigger.isInsert){
41. //after logic goes here...
42. }
43. }
44. }

We have covered only the before event.

What's happening?

Line 1 creates a new variable. A variable is just a temporary place where you can store a piece of data in Apex. It can store complex types such as Salesforce objects or simple types such as a string, an integer, or a date, for example.

Our first variable has the opportunity type, and it will contain the current opportunity record (supposing only one record is in the context of current trigger execution) that is taken from the Trigger.new property.

This property is a list of records of the opportunity type that contains all records that are part of the current trigger execution (and that have the most recent field values). A list is just a complex Apex type that is used to store a set of items of the same type. These elements can be taken using the [n] operator, where n is the element's index, which spans from 0 (the first element) to the maximum number of elements minus 1 (the last element).

Basically, on line 1 we are storing the first element of the saved opportunities in a single variable of the opportunity type called currentOpp.

On line 8, the before event section starts, and on line 11 we are executing a piece of code for insert and update operations. Basically, if the current opportunity amount is less than 0, we add an error to the opportunity stating that the value is not correct. This check could be done using validation rules as well.

The addError() method (a method is another way of calling a function, as you saw with formulas and validation rules, but methods are related to a specific Apex object) is used to attach an error to the record (that is displayed on the page layout, as we'll see shortly), flagging it as not committable to the database, thus interrupting the order of execution. We saw this in the previous section.

From line 23, we are concentrating on the before update condition (that is, if we create a record, this piece of code won't be executed). We can see a new variable that stores the old valued opportunity (Trigger.old is a list just like Trigger.new, but it contains the opportunities with the old values) and two new double variables that contain the new and old values.

The Trigger Apex class supports many static variables that can be used to understand the executing context: the most common ones are Trigger.newMap and Trigger.oldMap, which convey the same records as Trigger.new and Trigger.old but using a special Apex class called Map, which organizes records based on their Salesforce ID field (which in some algorithms is really useful). For more information, refer to Salesforce Help at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_System_Trigger.htm.

Why has the oldOpportunity variable been defined inside the Trigger.isUpdate if statement and not just after the currentOpp definition? This is because insert operations do not convey an old value (there is nothing on the database so far), thus accessing the Trigger.old list in an insert operation leads to a critical error on Apex execution, which interrupts trigger execution and prevents the whole save operation from closing.

The two newAmount and oldAmount variables are then compared, and if the new amount is less than 50% of the previous amount, an error is attached to the record (this match could have been implemented using a validation rule as well).

Let's test it out:

  1. Open the Developer Console (click the Settings icon and then select the Developer Console item). When the console loads, click on File | New | Apex Trigger, select the Opportunity sObject, and call it OpportunityTrigger:

Trigger creation
  1. Now copy the whole code in the previous example (or cut and paste it from the book's GitHub repository) and click File | Save or just Ctrl + S on your keyboard. If you have made some errors while writing your code, you'll see where the error lies on the Problems tab:

List of problems generated by miswritten code

Line 5 simply didn't terminate with a semicolon (required by Apex syntax), and this leads to a chain of errors on the subsequent lines: if we add the semicolon, all the compile errors disappear.

Do you remember debug logs from Chapter 2, Auditing and Monitoring? When we open the Developer Console, a new debug log is opened on the fly (have a look at the Logs tab):

Debug logs on the Developer Console
  1. Now that the trigger is in place, don't close the Developer Console window. Instead, jump to the CRM and create a new opportunity (or update an existing one) with a negative amount:

Trigger error for negative amount value
  1. Now save the opportunity with a positive value (let's say $1,000) and then try to update the amount with $400 (which is lower than 50% of the original value):

Trigger error for business rule on the amount field

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
  1. 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 4Extending 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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.199.122