Does this picture have anything directly to do with an APEX Trigger? Of course not. However, it does accurately depict the frustration I experienced the absolute first time I tried to write a Trigger. (Bonus points if you have seen Bloodsport…)
The purpose of this article is to provide a declarative Admin (a.k.a “ButtonClickAdmin“) a decent primer to writing their first Trigger. I do need to however go through the “declarative before programmatic” tenet – meaning that you always try to make something work through the Salesforce UI before going to a code / programmatic route. A lot of triggers I’ve seen out there were written before a lot of the new cool features of Salesforce have been released – keep in mind these things:
- Formulas
- Salesforce provides you a rich relational pathing ability to grab fields of a Parent or Lookup simply by building it through a formula
- Workflows
- You can do Field Updates, Tasks, Emails, and Outbound Messages using Workflow
- As of Spring ’12, you can now do “cross-object” Field Updates – before Spring ’12 a LOT of Triggers I saw existed just to do this (page 152 of the Spring ’12 Release Notes)
- Validation Rules
- Apart from the cross-object stuff, this is the other area where a lot of Triggers are written
- Which can be OK if you have CRAZY complex computations to perform that you just can’t do with a V-Rule formula…
- Use V-Rules to do your traditional data validation on fields
- Apart from the cross-object stuff, this is the other area where a lot of Triggers are written
Salesforce does have some great documentation to get you started:
I’m going to pick and pull apart the documentation to give you an example of how I wrote a trigger just a while back to automatically add a Chatter post to a parent record when a child record was created. You could really take this example and apply it to creating a record in any business case however.
First however, let’s dive into how a Trigger is structured…
Structure of a Trigger
trigger AddChatterPostToParent on ChildObject__c (after insert, after update) {
This is defining your trigger. It’s stating that this is:
- a Trigger.
- “AddChatterPostToParent” is the name of this trigger
- “ChildObject__c” is the object that this trigger will be firing against
- The database operations that this trigger will fire on – your choices are “insert, update, delete, and undelete”
- “before” = you have the ability to CHANGE values of fields
- “after” = you do NOT have the ability to change values of fields
- Great post from Salesforce on the pros vs. cons on before/after HERE
When I’m typically writing triggers, I like to put some comments at the beginning to describe what I’m trying to accomplish:
trigger AddChatterPostToParent on ChildObject__c (after insert, after update) {
//////////////////////////////////////
// This trigger creates a Chatter Feed item for the parent record
// when a child record is created
// Created 20120718 Andy Boettcher
// Modified 20120718
///////////////////////////////////////* TRIGGER LOGIC WILL GO HERE */
}
Resources Available to You
When writing a trigger, you have a few objects already ready for you to use:
Collections
- “trigger.new” – this is a collection of the new records you are working with.
- “trigger.newMap” – this is an indexed collection (map) of the new records you are working with.
- “trigger.old” – this is a collection of the OLD values of the records you are working with, such as in the case of an update.
- “trigger.oldMap” – this is an indexed collection (map) of the old records you are working with.
True/False Variables
- “What kind of action am I working with?”
- Trigger.isInsert
- Trigger.isUpdate
- Trigger.isDelete
- Trigger.isUnDelete
- “Is this a before or after action?”
- Trigger.isBefore
- Trigger.isAfter
// Run this code if we’re dealing with an Insert only
if(Trigger.isInsert) {
blah blah blah
}// Run this code if we’re dealing with an Update only
if(Trigger.isUpdate) {
blah blah blah
}// Run this code if we’re dealing with either an Insert or Update only
if(Trigger.isInsert || Trigger.isUpdate) {
blah blah blah
}
** GOTCHA ALERT! **
Here’s one of the biggest “gotchas” you’ll encounter working with APEX and Triggers – you are already definitely aware that Salesforce has “limits” on just about everything; Limits on objects, fields, data size, etc. The same goes for APEX, but you have to account for it yourself in your code vs. having Salesforce nicely remind you when you’re near the threshold. The official term is “bulkification” – meaning that our code has to be able to handle anywhere from a single record up to thousands without hitting a limit. I will absolutely show and describe how we are dealing with this in the examples that follow.
Trigger Logic
The best way I can suggest bridging the concept of writing a formula / workflow in Salesforce and writing a Trigger is to “speak” what you want to do. As you’re writing the Trigger, it’s very easy to get stuck on a tangent and over-code your solution. Speak it, write it down, and then go through your code when you’re done to make sure you didn’t get off track.
- “On all records coming through my trigger, I want to create a Chatter Post on the parent object.” (this is our example)
- “On only the records with a value of “Tuesday” on the “Day Of Week” field, I want to create a new record in the “X” object.”
Putting the Trigger Together
trigger AddChatterPostToParent on ChildObject__c (after insert, after update) {
//////////////////////////////////////
// This trigger creates a Chatter Feed item for the parent record
// when a child record is created
// Created 20120718 Andy Boettcher
// Modified 20120718
//////////////////////////////////////// Create Empty List to hold new items (bulkification)
List<FeedItem> lstFeeds = new List<FeedItem>();// Loop through Comments and create Feeds
for(ChildObject__c c : trigger.new) {FeedItem fi = new FeedItem();
fi.Type = ‘TextPost’;
fi.ParentId = c.ParentField__c;
fi.Body = ((Trigger.isInsert) ? ‘created’ : ‘updated’) + ‘ a Child Object on this Parent.’;lstFeeds.add(fi);
}
// Insert List if there are records contained within
if(lstFeeds.size() > 0) { insert lstFeeds; }}
Those Pesky Unit Tests
With any APEX code, you need to write a Unit Test before you can deploy this new trigger to a Production environment. Your Unit Test is meant to basically emulate how your code would be executed, and is structured to test if your desired result is what happens to come out of it.
To create a Unit Test for this Trigger, we need to create a Class that creates a Parent Object first, then creates a Child Object related to the Parent. To be 100% kosher, we need to then update the new Child Object to cover both sides of the “if” condition for the Chatter Post body.
@isTest
private class AddChatterPostToParent_UnitTest {static testMethod void testTrigger() {
// Create Parent Object
ParentObject__c p = new ParentObject__c();
p.Name = ‘Test Parent’;insert p;
// Tell Salesforce that we are starting our test now
test.startTest();ChildObject__c c = new ChildObject__c();
// Relate this Child to its Parent from the inserted record above
c.ParentId = p.Id;c.Name = ‘Test Child’;
// Insert Child Record
insert c;// Update Child Record
update c;// Tell Salesforce we’re done with our test
test.stopTest();}
}
Writing your first Trigger is kind of like jumping into the deep end of the pool with a lead weight belt, but that’s what your Sandbox is for. Tinker, learn, ask questions…leverage the community. You’ll get it. =)