In this article, we will dive into Apex Triggers in Salesforce, making it easy to understand for students with little or no programming experience. The goal is to introduce you to triggers, their uses, and how to write efficient triggers in Salesforce.
What is an Apex Trigger?
An Apex Trigger is a piece of code that runs automatically when specific events occur in Salesforce, such as creating, updating, or deleting a record. Triggers are used when you want Salesforce to perform actions based on these events.
Why Use Apex Triggers?
- Automating Processes: You can automate tasks, such as updating related records when one record is changed.
- Enforcing Business Logic: If you want to enforce custom rules that aren’t possible with validation rules, triggers can help.
- Custom Workflows: You can set up workflows that go beyond what standard Salesforce functionality offers.
Trigger Context Variables
Triggers provide several built-in variables to access the records being processed. Here are some important context variables:
Trigger.new
: Contains the new versions of the records being inserted or updated.Trigger.old
: Contains the old versions of the records being updated or deleted.Trigger.newMap
: A map of new records with their IDs as keys.Trigger.oldMap
: A map of old records with their IDs as keys.Trigger.isInsert
: Returnstrue
if the trigger was fired because of an insert operation.Trigger.isUpdate
: Returnstrue
if the trigger was fired because of an update operation.Trigger.isDelete
: Returnstrue
if the trigger was fired because of a delete operation.
Types of Apex Triggers
There are two main types of triggers:
- Before Triggers
- These triggers run before a record is saved to the database.Use Case: They are typically used to modify or validate data before it is saved.
trigger FormatAccountPhone on Account (before insert, before update) {
for (Account acc : Trigger.new) {
if (acc.Phone != null && acc.Phone.length() == 10) {
// Format phone number as (123) 456-7890
acc.Phone = '(' + acc.Phone.substring(0, 3) + ') '
+ acc.Phone.substring(3, 6) + '-'
+ acc.Phone.substring(6);
}
}
}
Explanation:
- Trigger Type: This trigger works for both before insert and before update operations.
- Context Variable:
Trigger.new
is used, which holds the list of new or updatedAccount
records. - Logic:
- We check if the phone number (
acc.Phone
) is not null and has exactly 10 digits. - If it does, we format it as
(123) 456-7890
using thesubstring
method. - This formatting happens before the record is saved to the database, ensuring that the phone number is saved in the desired format.
- We check if the phone number (
Test Class for Trigger
It’s important to have a test class that verifies the trigger works as expected:
@isTest
public class FormatAccountPhoneTest {
@isTest
static void testPhoneFormatting() {
// Create a new Account with a 10-digit phone number
Account acc = new Account(Name = 'Test Account', Phone = '1234567890');
insert acc;
// Query the inserted account
Account insertedAcc = [SELECT Id, Phone FROM Account WHERE Id = :acc.Id];
// Verify that the phone number was formatted correctly
System.assertEquals('(123) 456-7890', insertedAcc.Phone);
// Update the account with a new phone number
insertedAcc.Phone = '0987654321';
update insertedAcc;
// Query the updated account
Account updatedAcc = [SELECT Id, Phone FROM Account WHERE Id = :insertedAcc.Id];
// Verify that the new phone number was formatted correctly
System.assertEquals('(098) 765-4321', updatedAcc.Phone);
}
}
Explanation of the Test Class:
- We create an Account with a phone number that has 10 digits.
- After inserting it, we query the Account to verify the phone number was formatted correctly by the trigger.
- We then update the Account with a new 10-digit phone number and check that it is also formatted correctly after the update.
- After Triggers
- These triggers run after a record has been saved to the database.Use Case: They are used when you need to work with data that is already saved, such as updating related records.
trigger UpdateAccountRevenue on Opportunity (after insert, after update) {
// Create a map to store Account Ids and their related total Opportunity amounts
Map<Id, Decimal> accountRevenueMap = new Map<Id, Decimal>();
// Loop through the newly inserted or updated Opportunity records
for (Opportunity opp : Trigger.new) {
if (opp.AccountId != null) {
if (!accountRevenueMap.containsKey(opp.AccountId)) {
accountRevenueMap.put(opp.AccountId, 0);
}
// Add the Opportunity's amount to the total revenue
accountRevenueMap.put(opp.AccountId, accountRevenueMap.get(opp.AccountId) + opp.Amount);
}
}
// Fetch related Account records
List<Account> relatedAccounts = [SELECT Id, Total_Revenue__c FROM Account WHERE Id IN :accountRevenueMap.keySet()];
// Update each Account’s total revenue field
for (Account acc : relatedAccounts) {
acc.Total_Revenue__c += accountRevenueMap.get(acc.Id);
}
// Perform the DML update on the Accounts
update relatedAccounts;
}
Explanation of the After Trigger:
- Trigger Type: This trigger works after
insert
andupdate
operations on the Opportunity object. - Trigger Context Variable: We use
Trigger.new
to access the newly created or updated Opportunity records. - Logic:
- We first loop through the
Opportunity
records inTrigger.new
and gather theirAccountId
to calculate the total amount of all related Opportunities. - We then fetch the related Accounts using a SOQL query and update their
Total_Revenue__c
field by adding the summed Opportunity amounts. - Finally, we perform the DML
update
on the related Account records to save the changes.
- We first loop through the
Test Class for After Trigger
Here’s a test class to verify that the after trigger works as expected:
@isTest
public class UpdateAccountRevenueTest {
@isTest
static void testRevenueUpdate() {
// Create a test Account
Account testAccount = new Account(Name = 'Tech Corp', Total_Revenue__c = 0);
insert testAccount;
// Insert a new Opportunity related to the test Account
Opportunity opp = new Opportunity(Name = 'New Deal', AccountId = testAccount.Id, Amount = 10000, StageName = 'Prospecting', CloseDate = Date.today());
insert opp;
// Query the Account and check the total revenue has been updated
Account updatedAccount = [SELECT Id, Total_Revenue__c FROM Account WHERE Id = :testAccount.Id];
System.assertEquals(10000, updatedAccount.Total_Revenue__c);
// Update the Opportunity amount
opp.Amount = 20000;
update opp;
// Query the Account again and check the total revenue has been updated
updatedAccount = [SELECT Id, Total_Revenue__c FROM Account WHERE Id = :testAccount.Id];
System.assertEquals(20000, updatedAccount.Total_Revenue__c);
}
}
Best Practices for Writing Apex Triggers
To write efficient triggers, follow these best practices:
- Bulkify Your Code: Triggers in Salesforce are often invoked for multiple records at once. Write your triggers to handle bulk operations by processing all records in
Trigger.new
orTrigger.old
.
Example of Bulkified Code:
trigger AccountPhoneFormat on Account (before insert, before update) {
for (Account acc : Trigger.new) {
if (acc.Phone != null && acc.Phone.length() == 10) {
acc.Phone = '(' + acc.Phone.substring(0, 3) + ') '
+ acc.Phone.substring(3, 6) + '-'
+ acc.Phone.substring(6);
}
}
}
- One Trigger per Object: Limit yourself to one trigger per object. You can manage multiple functionalities within a single trigger by using if-else conditions or separate handler methods.
- Avoid Hardcoding: Do not hardcode values like record IDs, field values, or URLs. Use custom settings or labels to make your code adaptable across different environments.
- Use Helper Classes: Move the business logic out of the trigger and into a separate class (known as a helper or handler class). This makes the code cleaner and easier to maintain.
Example:
trigger AccountTrigger on Account (before insert) {
AccountHelper.beforeInsert(Trigger.new);
}
public class AccountHelper {
public static void beforeInsert(List<Account> newAccounts) {
for (Account acc : newAccounts) {
if (acc.Phone != null && acc.Phone.length() == 10) {
acc.Phone = '(' + acc.Phone.substring(0, 3) + ') '
+ acc.Phone.substring(3, 6) + '-'
+ acc.Phone.substring(6);
}
}
}
}
- Test Thoroughly: Write unit tests that cover all possible scenarios for your triggers, including bulk operations and error conditions.
Conclusion
Apex Triggers are a powerful tool in Salesforce for automating business processes. By understanding the types of triggers, how to use context variables, and following best practices, you can write efficient and scalable code that enhances your Salesforce applications.