All Projects → apexfarm → ApexTriggerHandler

apexfarm / ApexTriggerHandler

Licence: BSD-3-Clause license
Another library implements Apex trigger handler design pattern.

Programming Languages

Apex
172 projects

Projects that are alternatives of or similar to ApexTriggerHandler

Apex Recipes
A library of concise, meaningful examples of Apex code for common use cases following best practices.
Stars: ✭ 307 (+667.5%)
Mutual labels:  trigger, salesforce, apex
timeline-component-lwc
This component enables timeline view for Salesforce Record history.
Stars: ✭ 18 (-55%)
Mutual labels:  salesforce, apex
dreaminvest-lwc
Sample application for Lightning Web Components on Salesforce Platform. Part of the sample gallery. Financial services use case. Get inspired and learn best practices.
Stars: ✭ 41 (+2.5%)
Mutual labels:  salesforce, apex
sf-cross-cutting-concerns
Apex Cross cutting concerns for Salesforce
Stars: ✭ 29 (-27.5%)
Mutual labels:  salesforce, apex
apex-mocks-stress-test
Testing out FFLib versus Crud / CrudMock
Stars: ✭ 47 (+17.5%)
Mutual labels:  salesforce, apex
apex-rest-route
A simple framework for building Restful API on Salesforce
Stars: ✭ 75 (+87.5%)
Mutual labels:  salesforce, apex
R.apex
Functional utility library for Apex
Stars: ✭ 80 (+100%)
Mutual labels:  salesforce, apex
lwc-modules
Build any LWC you want without ever having to touch Apex
Stars: ✭ 20 (-50%)
Mutual labels:  salesforce, apex
apex-dml-mocking
DML mocking, CRUD mocking, dependency injection framework for Salesforce.com (SFDC) using Apex
Stars: ✭ 38 (-5%)
Mutual labels:  salesforce, apex
Apex-Code-Conventions
Apex conventions and best practices for Salesforce Developers
Stars: ✭ 28 (-30%)
Mutual labels:  salesforce, apex
apex-rollup
Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.
Stars: ✭ 133 (+232.5%)
Mutual labels:  salesforce, apex
apex-graphql-query
A library for building GraphQL queries in apex
Stars: ✭ 31 (-22.5%)
Mutual labels:  salesforce, apex
codeclimate-apexmetrics
ApexMetrics - Code Climate engine for Salesforce [DISCONTINUED use CC PMD instead)
Stars: ✭ 46 (+15%)
Mutual labels:  salesforce, apex
sfdc-error-playground
Lightning & Apex Error Playground
Stars: ✭ 30 (-25%)
Mutual labels:  salesforce, apex
universalmock
A universal mock class in Apex
Stars: ✭ 55 (+37.5%)
Mutual labels:  salesforce, apex
ApexCallouts
A lightweight Apex library for making HTTP callouts. Works with remote site settings and named credentials.
Stars: ✭ 32 (-20%)
Mutual labels:  salesforce, apex
Sfdx Mass Action Scheduler
🚀 Declaratively schedule Process Builder, Flows, Quick Actions, Email Alerts, Workflow Rules, or Apex to process records from Reports, List Views, SOQL, or Apex.
Stars: ✭ 200 (+400%)
Mutual labels:  salesforce, apex
Soqlx
SoqlXplorer is an awesome tool for developers using the Salesforce.com platform.
Stars: ✭ 220 (+450%)
Mutual labels:  salesforce, apex
apex-tmLanguage
Salesforce Apex Language syntax grammar used for colorization
Stars: ✭ 27 (-32.5%)
Mutual labels:  salesforce, apex
SFDCRules
Simple yet powerful Rule Engine for Salesforce - SFDCRules
Stars: ✭ 38 (-5%)
Mutual labels:  salesforce, apex

Apex Trigger Handler

 

There are already many trigger handler libraries out there, but this one has some different approaches or advantanges such as state sharing, built in helper methods etc.. Just one class Triggers.cls with its corresponding test class TriggersTest.cls, and its minimal and simple.


Release 1.1.3

Breaking changes to Triggers.Props Methods: All filterChanged() methods now return List<Id>, instead of Set<Id>. This is because List<Id> has wider use cases, it can not only be used in a where condition, but can also be used in an iteration.


Features

  1. Share common query results via context.state with the following handlers in the current trigger execution context.
  2. Built-in helpers to perform common operations on trigger properties, such as detect field changes.
  3. Control flow of handler execution with context.next(), context.stop(), and context.skips.

Usage

To create a trigger handler, you will need to create a class that implements the Triggers.Handler interface and its criteria method, and the corresponding trigger event method interfaces, such as the Triggers.BeforeUpdate interface and its beforeUpdate method.

public class MyAccountHandler implements Triggers.Handler, Triggers.BeforeUpdate {
    public Boolean criteria(Triggers.Context context) {
        return true;
    }

    public void beforeUpdate(Triggers.Context context) {
        // do stuff
    }
}

Trigger

As you have noticed, why we are creating same handlers for different trigger events? This is because handlers may need to execute in different orders for different trigger events, we need to provide developers great controls over the order of executions.

trigger AccountTrigger on Account (before update, after update) {
    Triggers.prepare()
        .beforeUpdate()
            .bind(new MyAccountHandler())
            .bind(new AnotherAccountHandler())
        .afterUpdate()
            .bind(new AnotherAccountHandler())
            .bind(new MyAccountHandler())
        .execute();
}

Trigger Handler

Please check the comments below for detailed explanations and tricks to customize a trigger handler.

// 1. Use interfaces instead of a base class to extend a custom handler. With interface
// approach we can declare only the needed interfaces explicitly, which is much cleaner
// and clearer.
public class MyAccountHandler implements Triggers.Handler,
                                         Triggers.BeforeUpdate,
                                         Triggers.AfterUpdate {

    // 2. There is a "criteria" stage before any handler execution. This gives
    // developers chances to turn on and off the handlers according to
    // configurations at run time.
    public Boolean criteria(Triggers.Context context) {
        return Triggers.WHEN_ALWAYS;

        // 3. There are also helper methods to check if certain fields have changes
        // return context.props.isChangedAny(Account.Name, Account.Description);
        // return context.props.isChangedAll(Account.Name, Account.Description);
    }

    public void beforeUpdate(Triggers.Context context) {
        then(context);
    }

    public void afterUpdate(Triggers.Context context) {
        then(context);
    }

    private void then(Triggers.Context context) {
        // 4. All properties on Trigger have been exposed to context.props.
      	// Direct reference of Trigger.old and Trigger.new can be avoided,
        // instead use context.props.oldList and context.props.newList.
        if (context.props.isUpdate) {

            // 5. Use context.state to pass query or computation results down to all
            // following handlers within the current trigger context, i.e. before update.
            Integer counter = (Integer)context.state.get('counter');
            if (counter == null) {
                context.state.put('counter', 0);
            } else {
                context.state.put('counter', counter + 1);
            }

            // 6. Use context.skips or Triggers.skips to prevent specific handlers from
            // execution. Please do remember restore the handler when appropriate.
            context.skips.add(ContactHandler.class);
            List<Contact> contacts = ...;
            Database.insert(contacts);
            context.skips.remove(ContactHandler.class);

            // 7-1. Call context.next() to execute the next handler. It is optional to use,
            // unless some following up logics need to be performed after all following
            // handlers finished.
            context.next();

            // 7-2. If context.stop() is called instead of context.next(), any following
            // handlers won't be executed, just like the STOP in process builder.
            context.stop();
        }
    }
}

More on Skips

context.skips references the same global static variable Triggers.skips. If you want to skip handlers in contexts rather than a trigger handler. Please use Triggers.skips instead. For example, when you want to skip a trigger handler in a batch class:

global class AccountUpdateBatch implements Database.Batchable<sObject> {
    ...
    global void execute(Database.BatchableContext BC, List<sObject> scope){
        Triggers.skips.add(MyAccountHandler.class);
        // Update accounts...
        Triggers.skips.remove(MyAccountHandler.class);
    }
    ...
}

Or you can skip the handler during batch execution in the criteria phase:

public class MyAccountHandler implements Triggers.Handler, Triggers.BeforeUpdate {
    public Boolean criteria(Triggers.Context context) {
        return !System.isBatch();
    }
    ...
}

Unit Test How-To

The following method is private but @TestVisible, it can be used in test methods to supply mock recoreds for old and new lists. So we don't need to perform DMLs to trigger the real triggers.

List<SObject> oldList = new List<SObject> {
    new Account(Id = TriggersTest.getFakeId(Account.SObjectType, 1), Name = 'Old Name 1'),
    new Account(Id = TriggersTest.getFakeId(Account.SObjectType, 2), Name = 'Old Name 2'),
    new Account(Id = TriggersTest.getFakeId(Account.SObjectType, 3), Name = 'Old Name 3')}

List<SObject> newList = new List<SObject> {
    new Account(Id = TriggersTest.getFakeId(Account.SObjectType, 1), Name = 'New Name 1'),
    new Account(Id = TriggersTest.getFakeId(Account.SObjectType, 2), Name = 'New Name 2'),
    new Account(Id = TriggersTest.getFakeId(Account.SObjectType, 3), Name = 'New Name 3')}

Triggers.prepare(TriggerOperation.Before_Update, oldList, newList)
    .beforeUpdate()
        .bind(new MyAccountHandler())
    .execute();

APIs

Trigger Handler Interfaces

Interface Method to Implement
Triggers.Handler Boolean criteria(Triggers.Context context);
Triggers.BeforeInsert void beforeInsert(Triggers.Context context);
Triggers.AfterInsert void afterInsert(Triggers.Context context);
Triggers.BeforeUpdate void beforeUpdate(Triggers.Context context);
Triggers.AfterUpdate void afterUpdate(Triggers.Context context);
Triggers.BeforeDelete void beforeDelete(Triggers.Context context);
Triggers.AfterDelete void afterDelete(Triggers.Context context);
Triggers.BeforeUndelete void afterUndelete(Triggers.Context context);

Triggers.Context

Property/Method Type Description
context.props Triggers.Props All properties on Trigger are exposed by this class. In addition there are frequently used helper methods and a convinient sObjectType property, in case reflection is needed .
context.state Map<Object, Object> A map provided for developers to pass any value down to other handlers.
context.skips Triggers.Skips A set to store handlers to be skipped. Call the following methods to manage skips: context.skips.add(), context.skips.remove(), context.skips.clear() context.skips.contains() etc.
context.next() void Call the next handler.
context.stop() void Stop execute any following handlers. A bit like the the stop in process builders.

Triggers.Props

Triggers.Props Properties

Property Type Description
sObjectType SObjectType The current SObjectType.
isExecuting Boolean Trigger.isExecuting
isBefore Boolean Trigger.isBefore
isAfter Boolean Trigger.isAfter
isInsert Boolean Trigger.isInsert
isUpdate Boolean Trigger.isUpdate
isDelete Boolean Trigger.isDelete
isUndelete Boolean Trigger.isUndelete
oldList List<SObject> Trigger.old
oldMap Map<Id, SObject> Trigger.oldMap
newList List<SObject> Trigger.new
newMap Map<Id, SObject> Trigger.newMap
operationType TriggerOperation Trigger.operationType
size Integer Trigger.size

Triggers.Props Methods

Note: the following isChanged method has the same behavior has the ISCHANGED formula:

  • This function returns false when evaluating any field on a newly created record.
  • If a text field was previously blank, this function returns true when it contains any value.
  • For number, percent, or currency fields, this function returns true when:
    • The field was blank and now contains any value
    • The field was zero and now is blank
    • The field was zero and now contains any other value
Method Type Description
- isChanged(SObjectField field1) Boolean Check if any record has a field changed during an update.
- isChangedAny(SObjectField field1, SObjectField field2)
- isChangedAny(SObjectField field1, SObjectField field2, SObjectField field3)
- isChangedAny(List<SObjectField> fields)
Boolean Check if any record has multiple fields changed during an update. Return true if any specified field is changed.
- isChangedAll(SObjectField field1, SObjectField field2)
- isChangedAll(SObjectField field1, SObjectField field2, SObjectField field3)
- isChangedAll(List<SObjectField> fields)
Boolean Check if any record has multiple fields changed during an update. Return true only if all specified fields are changed.
- filterChanged(SObjectField field1) List<Id> Filter IDs of records have a field changed during an update.
- filterChangedAny(SObjectField field1, SObjectField field2)
- filterChangedAny(SObjectField field1, SObjectField field2, SObjectField field3)
- filterChangedAny(List<SObjectField> fields)
List<Id> Filter IDs of records have mulantiple fields changed during an update. Return IDs if any specified field is changed.
- filterChangedAll(SObjectField field1, SObjectField field2)
- filterChangedAll(SObjectField field1, SObjectField field2, SObjectField field3)
- filterChangedAll(List<SObjectField> fields)
List<Id> Filter IDs of records have mulantiple fields changed during an update. Return IDs only if all specified fields are changed.

License

BSD 3-Clause License

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].