Cooking with Code: A Tasty Trigger Treat (Apex Triggers)

blog-triggers-woahWell! Here we are … facing one of the “big topics” in Apex coding. I have to say that I’m pretty darn excited (and yes, it’s true, I am easily excited). We’re finally getting to run some actual code … like … for realz!  It’s Apex trigger time!

Today, in Cooking with Code, we’re going to tackle the triggers. I think you’ll be amazed to see how that, for all the hype, they aren’t all that complicated. Really, you’ve done most of the hard stuff.

What is an Apex trigger?

A trigger is simply a bit of code that kicks off processes (code) when we change a record in Salesforce.

General Workflow for a TriggerThe whole purpose of a trigger is to wait around listening for the fun stuff (aka, inserts, updates, deletes, undelete) to happen. Then they leap up, and say, “Ohh, ohh! You just updated an Account record!  I know what to do…run this bit of apex code!” (See, triggers are just as excitable as I am!)

That’s why they’re called triggers, because they are “triggered” by some kind of database event. They are sort of like conditional logic:

  1. If an account is updated
    1. Do this …
    2. Then this …
    3. And then this …
  2. If an account is deleted:
    1. Do this …
    2. And then this (and so on …)

But what’s up with this before/after stuff?

There are two main types of triggers: before triggers and after triggers.

The structure of these two types of triggers are the same, but the difference is when they do their work, and the information (data) they have available to them.

Trigger Type What they do Great for
Before Triggers These effect the data in the record before it is committed (permanently saved) to the database. Update or validate field values on the record that you’re adding or updating, before you commit* them to the database. As you can imagine, this great for validating data and setting field values programmatically.*For my fellow word geeks, you may notice that I’m using the word “commit” versus “save.” Read this forum discussion to learn more. For a full run-down on the order of operations in Salesforce, check out this knowledgebase article. I think this would be a great future blog post! Don’t you?
After Triggers These use the data in the record after it has been committed to the database. Any time you need the ID or some other kind of field from the record you just created.A great example would be creating related records (e.g., creating a new Recipe Review record when you add a recipe to our custom Recipe object) <cue spooky foreshadowey music>

What happens in Before and After triggers

You should think of before Triggers as the default kind of trigger, because they are used way more often than after triggers.

They also have an added advantage of not needing DML to save your changes. With before triggers your changes to the data are made before the record is saved, so it’s like you’re sneaking in your change and then using Salesforce’s standard “save” functionality. Neat!

However, there are still plenty of times you will want to use an after trigger. In fact we’ll use on in our example below <foreshadowey music getting louder>

What does an Apex trigger look like?

The basic structure of a trigger is:

Trigger nameOfTheTrigger on SomeObject (whenYouWantTheTriggerToRun){
       // code to run here
 }

Pretty basic right? Most of the stuff that you’re actually doing is in the  "// code to run here" piece.

Let’s go over a few things that might not be so obvious.

An Apex trigger is specific to a single Salesforce object

Let’s modify our base trigger and add a few elements:

Trigger recipe on Recipe__c (whenYouWantTheTriggerToRun){
      // code to run here
 }

You can probably now see that there is only one object specified (Recipe__c).

That’s because triggers are specific to one (and only one) object. They can, of course, involve code that affects many other Salesforce Objects (like creating or updating records), but we’ll cover that later.

How do you say when you want the trigger to run?

You may want a particular piece of code to run whenever a new record is added to (inserted into) your database and when a record is updated.

A cool thing about Apex Triggers are that you don’t have to duplicate code to handle this (duplication = bad!).

Take a look at a revision to our code:

Trigger recipe on Recipe__c (after insert, after update){
      // code to run here
 }

You can probably tell what we did here. We asked the trigger, “When a recipe is added or updated in our database, then please run the following bit of code.” (We’re so polite!)

So what are our options? There are seven different events that triggers can listen for:

  • Before insert
  • Before update
  • Before delete
  • After insert
  • After update
  • After delete
  • After undelete (I know … that’s a cool one right?!)

Great, but where do I put all this code?

Where do we put the code we want to run when the Apex trigger is … well … triggered?

Triggers and Classes can be added to your Salesforce dev or sandbox org in a few ways. The easiest is to open your developer console and choose file > new > Apex Class (or Apex Trigger).

But what about where the //code to run goes here?

Does that just go in the trigger? Where does the class come in?

Two options for where to put the bulk of your code

blog-triggers-flow-bestpracticeAs with many things in coding, you have options (wouldn’t it sometimes be easier if you didn’t!).

With a trigger, you can either put the actual heavy lifting of your code:

  • Within the trigger itself.
  • In a separate Apex Class, which you call from within the trigger.

For lots of reasons the second way is part of Apex Best Practice, including:

  • It’s easier to reuse code. This is BIG! You can’t make methods in a trigger class (this is what we call the actual trigger). So if you want to use the same piece of code in multiple parts of your trigger (or in other triggers) then you’d have to duplicate your code in all the places you need to use it. As we know, duplication = bad, because if you want to change the code you would need to change it in all the places you have it listed.  And that would make for an oh-so-sad amount of work.
  • It’s easier to read the code and understand what’s going on. This is a huge part of efficient code maintenance. There is nothing more frustrating than going to back to a bit of your code and having it be so long and convoluted that you have to spend time trying to just understand what it does! Best practice is to keep all your business logic out of the actual trigger.
  • It’s easier to control the order of the code to run if you only have one trigger for each object. (This is a bigger topic for another post, but take my word for it for now).
  • It’s easier to write distinct unit tests when you’re dealing with smaller subsets of your code.

Best PracticesI’m going to show you both ways, with the best practices first.

The only reason I’m showing you the not-so-great-practices version is because you’ll see this pretty often as you troll the developer forums for coding solutions. This isn’t because it’s proposed by “bad” programmers. It’s because it’s sometimes easier to explain as a single chunk.

So I’ll show you the same code, but in both versions, and later you can use this as a guide for turning your code from “not-so-good practice” into “best practice.”

But! If this is confusing to you, just slide on by it and just look at the best practices version. You can always come back here later and take another look. Don’t worry…we’re not going anywhere!

“Best Practices” Trigger Version

As with any good coding job, we’ll first start with the question: What do we want our trigger to do?

Trigger Scope: Whenever a recipe record is added or updated in salesforce, check to see if there are any reviews for it (using a rollup summary count field that counts all related reviews). If there are no reviews, then add a review record with some default data (a record stub) and assign the review to whoever owns the recipe.

First, let’s check out a visualization of what our scope will be doing in code.

What's happening in our trigger

Clear? Hopefully, because if it isn’t we can’t write the trigger. But I think we’re all on the same page, so let’s tackle the trigger code! Woo hoo!

The trigger code

Let’s check back in with our example and see if we can figure out what’s going on.  Here is the entire trigger (no kidding!):

Trigger recipe on Recipe__c (after insert, after update){
       RecipeUtility.createReview (trigger.new);
}

What is this doing?

This trigger is running on the Recipe__c custom object, and fires any time after a recipe record is inserted or updated. When that happens, it will go to the RecipeUtility class and run the createReview method (passing it all the records that were just added or updated and committed – that’s the trigger.new piece; we’ll get to that later).

Awesome, so now let’s look at the RecipeUtility class that does the main bulk of the work.

The RecipeUtility class code

Below I’ve listed the entire code, and then we’ll break it down step by step.

Public class RecipeUtility {

   //create a new review stub record if the recipe hasn't been reviewed
   Public static void createReview(List<recipe__c> listRecipes) {

   //set up Due Date variable, which is 1 month from today
   Date dDate = Date.today(); //sets today's date
        dDate = dDate.addMonths(1); //adds one month

   //create an empty reviews list/array to collect all the reviews to be added
   List<Review__c> newReviews = new List<Review__c>();

   //loop through the recipes that were just created or updated
   For (Recipe__c recipe:listRecipes){

      //check against the rollup summary field that counts the number of reviews
      if(recipe.numberOfReviews__c < 1) {
         system.debug('=== Recipe needs a reviewer: '+ recipe.Name);

         //create a new review with due date and
         //assign review to the recipe owner
         Review__c myRev = new Review__c(
            name = 'Review of ' + recipe.Name,
            status__c = 'Not Started',
            dueDate__c = dDate,
            recipe__c = recipe.ID,
            ownerID__c = recipe.OwnerId
         );

         //add this new review sObject to our list of reviews
         newReviews.add(myRev);

         } // end if
      } //end for loop

   //insert the batch of new reviews into salesforce
   Insert newReviews;

   } //end createReview method
} // End recipeUtility class

Let’s break it down!

Breaking it down!If you read the comments (commenting = happiness later!), you can probably tell what’s going on here, but let’s break it down further:

  1. First we create the class called RecipeUtility.

    Public class RecipeUtility {
  2. There is only one method in this class: createReview. It takes in a List of recipe__c sObjects, which are the records that were updated or inserted. If we were inserting or updating records through the Salesforce UI/website, then this would be one record at a time, but if we imported a batch of records, then it could be many thousands of records.
    Public static void createReview(List<recipe__c> listRecipes) {
  3. Next we are creating a couple of variables:
    1. dDate is the date variable to store the due date for the review. I could have created this in a single line, but decided to do it over two because it’s easier to read. The first line sets the dDate variable to today’s date, and the second line uses the dDate value that was set and adds a month.
      Date dDate = Date.today(); // sets today's date 
           dDate = dDate.addMonths(1); // adds one month

      The other cool thing you’ll notice is that we are using the variable name (dDate) on both the right and the left sides of the equation, so we can use the variable value on the right, and reassign it back to itself on the left. Neat!

    2. newReviews is another empty list, of the datatype of Review__c. This will hold all the reviews records that we want to insert into our Review__c custom object
      List<Review__c> newReviews = new List<Review__c>();
  4. Now we loop over each of the recipes sObjects in our listRecipies list …
    For (Recipe__c recipe:listRecipes)
  5. … and look at the NumberOfReviews__c field (which is a Rollup Summary count of the number of reviews each recipe has), and if it is less than 1, then…
    if(recipe.numberOfReviews__c < 1) {
  6. We write an entry to the debug log.
    System.debug('=== Recipe needs a reviewer: '+ recipe.Name);
  7. Then we create a new review__c sObject called myRev with some default data.
    Review__c myRev = new Review__c(

    Including:

    1. The name of the review (name), which is a concatenation of some text (“Review of “) and the name of the recipe (note that the “recipe.” refers to the alias we provided in the for loop on line 14.
      name = 'Review of ' + recipe.Name,
    2. The status of the review (status__c) is set to “Not Started.”
      status__c = 'Not Started',
    3. The due date or the review (dueDate__c), which uses dDate variable we defined on lines 7-8.
      dueDate__c = dDate,
    4. The recipe the review should link to (recipe__c), again using “recipe.” dot notation. This is why we needed to use “after” triggers, because if we were inserting a record, the ID would only exist after the record was added to Salesforce.
      recipe__c = recipe.ID,
    5. The review owner (ownerID__c) is the same as the recipe owner and end the creation of the review.
      ownerID__c = recipe.OwnerId
      );
  8. We then close the if conditional statement and for loop and then use DML to send the newReviews list up to Salesforce, which inserts all the records the list contains.
    Insert newReviews;
  9. We then end the method and the class
       } //end createReview method
    } // End recipeUtility class

Phew!  It seems like a lot, but once you start collecting code snippets like this, you can use them for templates for other types of similar activities.

Quiz Question!

Pop QuizWhy did we go to all the trouble of creating a second list (newReviews) and put all our reviews in that? Why didn’t we just insert each of the reviews as we found ones that needed to be created?

  1. Because we love lists and can’t get enough of them?
  2. Because we want impress our friends and family with how fancy and complicated our code looks?
  3. Because we never want to put a DML statement within a for loop.

Drum roll please <brrrrrbrrrrrbrrrr>

The correct answer is C (although I do actually like lists and I do think this code is pretty special).

We never want to loop over a DML statement. Why? Because you never know how many records we might be looping over.

If we were adding or updating just one record within Salesforce it would be no biggee, but imagine what would happen if we imported 200 or a thousand?

Our good friend the Governor Limits would put up its big “talk to the hand” sign and stop your transaction from working after 150 DML statements. Boo hoo!

So instead, we create a list, and add all our records to be created into it, and then do just one DML statement with the whole shebang! Neat!

The Not-So-Best Practices Version

Ah ohh!The code for running everything together in one trigger, is mostly the same. Again, the only reason I’m showing you this, is because you’ll see it around a lot, and it’s good to know what you’re looking at, and how it relates to the “best practice” version.

Here’s the code (note that lines 3-33 below are the same as lines 7-37 in the “best practices” version above):

Trigger recipe on Recipe__c (after insert, after update){
   //set up Due Date variable, which is 1 month from today
   Date dDate = Date.today(); // sets today's date
        dDate = dDate.addMonths(1); // adds one month

   //create an empty reviews list/array to collect all the reviews to be added
   List<Review__c> newReviews = new List<Review__c>();

   //loop through the recipes that were just created or updated using trigger.new
   For (Recipe__c recipe : trigger.new){

      //check against the rollup summary field that counts the number of reviews
      if(recipe.numberOfReviews__c < 1) {
      system.debug('=== Recipe needs a reviewer: '+ recipe.Name);

         //create a new review with due date and
         //assign review to the recipe owner
         Review__c myRev = new Review__c(
            name = 'Review of ' + recipe.Name,
            status__c = 'Not Started',
            dueDate__c = dDate,
            recipe__c = recipe.ID,
            ownerID__c = recipe.OwnerId
         );

         //add this new review sObject to our list of reviews
         newReviews.add(myRev);

      } //end if statement
   } //end for loop

   //insert the batch of new reviews into salesforce
   Insert newReviews;

} //end trigger

Other Cool Trigger Stuff

What’s this trigger.new thing?

There are a number of variables that are always available to your triggers. These are called Trigger Context Variables and trigger.new is one of them.

I’m not going to cover all of the Context Variables (another blog post!), but stealing quite heavily from the Salesforce Documentation, below are some of the most helpful are:

Variable Use Usage Notes
new List of new versions of the sObjects (records) that were included in the trigger (e.g., all the values of all the fields in the records). Trigger.new can only be used in triggers where we have access to the new record.For example, if we were deleting a record, we can’t use trigger.new, because the record no longer exists (there is no “new” values in something we just deleted).You can access trigger.new in before update, before insert, after update, and after insert triggers. But remember, if you want to modify the data before it is committed to the database, then use the before trigger versions.
old List of old versions of the sObjects (records) that were included in the trigger Trigger.old can be used for update or delete triggers.The reason why trigger.old isn’t available for “insert” actions is because when we create new records, there is no “old.”
newMap Map of the IDs to the new versions of the sObjects (records) Trigger.newMap can be used for before update, after insert, and after update triggers.Again, no delete, because as with trigger.new, there is no new data in a deleted record because it no longer exists.
oldMap Map of the IDs to the old versions of the sObjects (records) Trigger.oldMap can be used for Update and delete triggersWhy it is not available for other insert triggers? Can you see a theme of when to use old vs new?It’s all about when the data is available.Old data isn’t available for records that you just created (inserted). New data isn’t available for data that you just deleted.

There are other trigger context variables that refer to the state of the trigger (isInsert, isUpdate, isBefore, isAfter, etc.), but we’ll leave them be for now, and cover them in a future blog post (see how I leave you wanting more!).

How can we use trigger context variables?

There are a ton of ways that context variables help us.

In our trigger example we used trigger.new because we wanted to get the new values that were either inserted or updated in our transaction.

What we’re actually feeding the class is a list variable of all the records that were created, and you can probably see on line 4 of the class where it creates a new list “listRecipes.”

Comparing record valuesAnother things you can do with trigger.old and trigger.new (and the map versions) is use them to see what changed.

David Liu has a great blog post about comparing old and new values in a trigger. This allows you to look for values that have changed, and depending on if they did or not, then run certain code. The important, but subtle thing here is that he’s NOT referring to when the value IS something, but rather, when the value changes to BECOME something.

As David points out, this is the same functionality that #awesomeAdmins know/use already in workflows, when they select “created, and any time it’s edited to subsequently meet the criteria.”

I definitely recommend checking out his post.

Wrapping up

blog-triggers-outroThis post is a happy completion of sorts. Triggers are the penultimate (love that word!) piece of Force.com Coding concepts. We have much more to explore, but the only concept we really haven’t touched on yet is our ultimate goal – code coverage! We’ll be covering that (pun intended) next post.

But for now, let’s summarize what we’ve learned about triggers:

  • Triggers are code that listens for Salesforce database events.
  • These events include inserts, updates, deletes, and undeletes.
  • The trigger can affect data before it is committed to our database (saved), or after it is committed.
  • Most of the time you’ll work with before triggers, but if you want to use the ID for the record you’ve just inserted, then you have to use an after trigger.
  • Best practices are to keep business logic code out of triggers, and put that in a separate classes.
  • There are list and map variables that come with every trigger that hold the old version of the data (trigger.old and trigger.oldMap) and the new version of the data (trigger.new and trigger.newMap).
    • The lists hold all the data
    • The maps just hold the IDs to the records

Wow! This was awesome. Hopefully you followed along, and can now check out some of the sample triggers and start to understand what they’re doing.

Thanks so much for joining me for this tasty trigger treat.

Until next time! Happy cooking in the Code Kitchen!