-
Notifications
You must be signed in to change notification settings - Fork 0
/
site.json
1 lines (1 loc) · 32.2 KB
/
site.json
1
{"title":"Trap.apex","githubRepo":"Click-to-Cloud/Trap.apex","googleAnalytics":"","index":{"title":"Home","description":" Trap.apex is a versatile functional reactive library to handle Salesforce triggers.","content":" Stream Style Trap.apex uses stream-based API to handle trigger logic. Trigger Control Trap.apex executes fine controls over trigger handlers. Easy Unit Test Trap.apex makes it easy to do unit test with triggers. ","srcFilePath":"src/pages/index.soy","id":"pages","location":"/./","url":"/trap-apex/./","children":{"docs":{"title":"Docs","description":"Everything you need to know to get started.","content":" Docs Start learning how to leverage the power of . Choose a Guide Each one provide step by step coverage for every core feature. ","srcFilePath":"src/pages/docs/index.soy","id":"docs","location":"/docs/","url":"/trap-apex/docs/","children":{"search":{"title":"Search","description":"Find what you're looking for in the documentation.","hidden":true,"content":" Electric Docs Start learning how to leverage the power of . ","srcFilePath":"src/pages/docs/search.soy","id":"search","location":"/docs/search.html","url":"/trap-apex/docs/search.html"},"Trigger_Controller":{"title":"Trigger Controller","description":"Trigger Controller","layout":"guide","icon":"cloud","weight":4,"content":" {$page.description} Trigger Controller Any instance of Trap works as a trigger controller. Basically, we use trigger controllers to find trigger handlers. TriggerHandler handler = Trap.getInstance().find('Case'); This, by default, will find the trigger handler named CaseTrigger. If the trigger controller fails to find an existing instance of CaseTrigger, it will create one and store it. Otherwise it will load the existing one. We can alter the default naming convention like this: TriggerHandler handler = Trap.getInstance('Handler').find('Case'); In this way, the trigger controller will look for CaseHandler instead of the default CaseTrigger. Methods are like: | Method | Description | | ------ | ----------- | | Trap.TriggerHandler find(String) | Find the trigger handler | | void start() | Start the trigger controller with default values from real trigger context | | void start(Trap.Event, List<SObject>, List<SObject>) | Start the trigger controller with given values | How Triggers Handlers are Controlled Every trigger handler created in this way is in fact associated with the trigger controller, and is hence managed by this trigger controller. Trigger handlers created in the integration style are therefore not managed by trigger controllers. Trigger Enablement Trap.apex actually has a hierarchy to control trigger handler enabled status. The hierarchy goes from top to bottom is: Global Enablement We can disable/enable the triggers globally. This setting will be overridden by specific trigger enablement and activeness, though. Trigger Enablement Each trigger handler can be disabled/enabled when they are enabled globally. Trigger Activeness Trigger handlers can work only when they are active and enabled. See isActive() method on trigger handler. Methods are like: | Method | Description | | ------ | ----------- | | Boolean isEnabled(String) | Check if the trigger is enabled | | Trap setEnabled(String, Boolean) | Set the trigger enablement | | Trap enable(String) | Enable the trigger | | Trap disable(String) | Disable the trigger | | Trap enableAll() | Enable globally | | Trap disableAll() | Disable globally | Example: Trap.getInstance() .enable('Case'); Trigger Max ReEntry In Trap.apex, we can set max re-entry to control how many times a trigger handler can be re-entered. Trap.getInstance() .setMaxReEntry('Case', 3); Methods are like: | Method | Description | | ------ | ----------- | | Trap setMaxReEntry(String, Integer) | Set the max re-entry | | Integer getMaxReEntry(String) | Get the max re-entry | Trigger New Transaction We can event set the trigger handler to use new transactions, so that the failure will only rollback within the trigger. Trap.getInstance() .setUsingNewTransaction('Case', true); Methods are like: | Method | Description | | ------ | ----------- | | Trap setUsingNewTransaction(String, Boolean) | Set whether to use new transaction | | Boolean isUsingNewTransaction(String) | Check if using new transaction | ","srcFilePath":"src/pages/docs/Trigger_Controller/index.md","id":"Trigger_Controller","location":"/docs/Trigger_Controller/","url":"/trap-apex/docs/Trigger_Controller/"},"Trigger_Execution":{"children":{"bulk_object":{"title":"Bulk Objects","description":"Bulk Objects","layout":"guide","icon":"code-file","weight":2,"content":" {$page.description} Bulk Objects Bulk objects help us manage the data in the trigger context. Here is how we typically use the bulk objects. Trap.BulkObject bo = new Trap.BulkObject(); bo.newStream .filter(new CustomFilterFunc()) .subscribe(Trap.F.updateField.apply('Subject', 'New subject')); for(Case c : caseList) { bo.newStream.next(c); } We can see that we don't necessarily need to use bulk objects inside triggers - they can be used standalone. Fundamentally a bulk object is a container with two streams(old stream and new stream) and one shared data, which we will see later. Data Streams Bulk objects contain two streams: oldStream This stream contains all the data from the old list in the trigger context. newStream This stream contains all the data from the new list in the trigger context. ","srcFilePath":"src/pages/docs/Trigger_Execution/bulk_object.md","id":"bulk_object","location":"/docs/Trigger_Execution/bulk_object.html","url":"/trap-apex/docs/Trigger_Execution/bulk_object.html"},"data_sharing":{"title":"Data Sharing","description":"Data Sharing","layout":"guide","icon":"code-file","weight":4,"content":" {$page.description} Data Sharing Bulk objects help us manage the data shared inside. The basic idea is that you run a function, get the data and store it in the bulk object. The process, for the same piece of data, will only be triggered once. And then you can load the data from the bulk object in whatever way you like. Below are methods to access the data: | Method | Description | | ------ | ----------- | | Object getData(String) | Get the data | | Trap.BulkObject setData(String, Object) | Set the data | | Boolean containsData(String) | Check if the data is contained | Example: // ... List accounts = [ SELECT Id FROM Account WHERE Id IN = : ids ]; bulkObj.setData('Accounts', accounts); // ... accounts = (List)bulkObj.getData('Accounts'); Set Data in Trigger Streams Trap.apex offers extra care when setting data in trigger streams. We can set raw data directly into the bulk object. bulkObj.newStream .tap(bulkObj.data('data', 'test')); Or set the computed data from the function. bulkObj.newStream .tap(bulkObj.data('accounts', new CustomGetAccountsFunc(), 'ids')); Here the bulk object will first load data indexed by ids and send it to CustomGetAccountsFunc. After that, it will set the computed value to accounts. The returned function from bulkObj.data is passed to tap and the real execution is hence deferred to when the stream actually runs. Similar methods are: | Method | Description | | ------ | ----------- | | Func data(String, Object) | Set the raw data | | Func data(String, Func, List<String>) | Set the data from the func and arg names | | Func data(String, Func) | Set the data from the zero-arg func | | Func data(String, Func, String) | Set the data from one arg func | | Func data(String, Func, String, String) | Set the data from the two arg func | | Func data(String, Func, String, String, String) | Set the data from the three arg func | Get Data in Trigger Streams Similarly, we have support when getting data in trigger streams. Here is how we load the data directly. bulkObj.newStream .tap(bulkObj.data('accounts'); You can use it like this, but in fact this is rarely used, as it actually turns every SObject into the data of account list. The other way is by providing data to functions. bulkObj.newStream .filter(bulkObj.provide('accounts', new CustomFilterFunc())); Here we first load the data indexed by accounts, and then send that as well as the current SObject to our CustomFilterFunc, and wrap this logic as a returned function. Similar methods are: | Method | Description | | ------ | ----------- | | Func data(String) | Get the data | | Func provide(List<String>, Func) | Provide args to the func | | Func provide(String, Func) | Provide the arg to the func | | Func provide(String, String, Func) | Provide the args to the func | | Func provide(String, String, String, Func) | Provide the args to the func | ","srcFilePath":"src/pages/docs/Trigger_Execution/data_sharing.md","id":"data_sharing","location":"/docs/Trigger_Execution/data_sharing.html","url":"/trap-apex/docs/Trigger_Execution/data_sharing.html"},"function":{"title":"Functions","description":"Functions","layout":"guide","icon":"code-file","weight":3,"content":" {$page.description} updateField Update the field value of the SObject, by giving a new value or an updating function. bulkObj.newStream .subscribe(Trap.F.updateField.apply('Subject', 'New subject')); addError Add an error to the SObject. bulkObj.newStream .subscribe(Trap.F.addError.apply('test error')); validate Do validation against the SObject. bulkObj.newStream .subscribe(Trap.F.validate.apply(R.propSatisfies.apply('Subject', R.isNotNull), 'Should not be null')); getOld Get the old value. bulkObj.newStream .filter((Func)R.pipe.run( Trap.F.getOld, new CustomFilterFunc() )) .subscribe(...); getNew Get the new value. bulkObj.oldStream .filter((Func)R.pipe.run( Trap.F.getNew, new CustomFilterFunc() )) .subscribe(...); changed Check if a field has changed value. bulkObj.newStream .filter(Trap.F.changed.apply('Subject')) .subscribe(...); isEvent Check current trigger event. bulkObj.newStream .filter(Trap.F.isEvent.apply(Trap.Event.BeforeInsert)) .subscribe(...); isBeforeInsert Check if it is the before insert event. bulkObj.newStream .filter(Trap.F.isBeforeInsert) .subscribe(...); isBeforeUpdate Check if it is the before update event. bulkObj.newStream .filter(Trap.F.isBeforeUpdate) .subscribe(...); isBeforeDelete Check if it is the before delete event. bulkObj.newStream .filter(Trap.F.isBeforeDelete) .subscribe(...); isAfterInsert Check if it is the after insert event. bulkObj.newStream .filter(Trap.F.isAfterInsert) .subscribe(...); isAfterUpdate Check if it is the after update event. bulkObj.newStream .filter(Trap.F.isAfterUpdate) .subscribe(...); isAfterDelete Check if it is the after delete event. bulkObj.newStream .filter(Trap.F.isAfterDelete) .subscribe(...); isAfterUndelete Check if it is the after undelete event. bulkObj.newStream .filter(Trap.F.isAfterUndelete) .subscribe(...); ","srcFilePath":"src/pages/docs/Trigger_Execution/function.md","id":"function","location":"/docs/Trigger_Execution/function.html","url":"/trap-apex/docs/Trigger_Execution/function.html"}},"title":"Trigger Execution","description":"Trigger Execution","layout":"guide","icon":"code-file","weight":2,"content":" {$page.description} Trigger Execution What distinguishes Trap.apex from all the other trigger frameworks is that its trigger execution is based on streams, a concept based on the functional reactive world. To dive in, please check out Stream.apex. Bulk Objects The core of Trap.apex trigger execution is the bulk objects. Bulk objects are objects that encapsulate data in the forms of streams. Stream Functions All Func objects can be used with streams, and therefore they can be used here. Besides, Trap.apex provides some utility functions that make our life easier. Data Sharing It's important to load and store data in the trigger execution, to avoid doing query inside the loop. Bulk objects have some nice features to support this. ","srcFilePath":"src/pages/docs/Trigger_Execution/index.md","id":"Trigger_Execution","location":"/docs/Trigger_Execution/","url":"/trap-apex/docs/Trigger_Execution/","childIds":["bulk_object","function","data_sharing"]},"Trigger_Handler":{"title":"Trigger Handlers","description":"Trigger Handlers","layout":"guide","icon":"flash","weight":1,"content":" {$page.description} What is a Trigger Handler? A trigger handler is an object that processes the trigger logic. Following the trigger best practices, we separate the logic from triggers into trigger handlers. First, we delegate the logic from within triggers to trigger handlers. trigger CaseTrigger on Case ( before insert, before update, before delete, after insert, after update, after delete, after undelete ) { Trap.getInstance().start(); // Start the trap } Here we get the instance of Trap, which is a trigger handler controller, and it will manage the lifecycle of trigger handlers and delegate the trigger execution context to the trigger handler. Then we create a trigger handler like this: public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void setUpBeforeInsert(Trap.BulkObject bulkObj) { bulkObj.newStream .filter(new CustomFilterFunc()) .subscribe(Trap.F.addError.apply('test error')); } } For details on the streams and bulk objects here, please check out 'Trigger Execution' section. Trigger Context One of the key concepts in Trap.apex is that we use separate trigger contexts from the standard Salesforce trigger contexts. This means that we no longer have direct dependencies over Salesforce apex API, which further enables us to do our unit test simply, as we have complete control of our data. Normally we bootstrap Trap.apex like this: Trap.getInstance().start(); // Start the trap And we can also explicitly set the trigger context data during the bootstrap. Trap.getInstance().start(Trap.Event.BeforeInsert, null, new List{ new Case() }); So we can easily control the trigger event, old list and new list passed into the trigger context. And we apply that into the unit testing. @isTest private static void contextTest() { // test code Trap.getInstance().start(Trap.Event.BeforeInsert, null, new List{ new Case() }); // test code } What we need to bear in mind is that in our code, do not use code like Trigger.Xxx. Instead, use this: Trap.Context triggerContext = this.getTriggerContext(); Map newMap = triggerContext.newMap; List newList = triggerContext.newList; Methods are below: | Method | Description | | ------ | ----------- | | Trap.Context Trap.TriggerHandler.getTriggerContext() | Get the trigger context | | Boolean Trap.TriggerHandler.isActive() | Check if the trigger handler is active | | void Trap.TriggerHandler.run() | Run the trigger handler with default values from the real trigger context | | void Trap.TriggerHandler.run(Trap.Event, List<SObject>, List<SObject>) | Run the trigger handler with the trigger event, old list and new list | Trigger Event Trap.apex categorizes trigger events into: BeforeInsert BeforeUpdate BeforeDelete AfterInsert AfterUpdate AfterDelete AfterUndelete All All these events can be found in Trap.Event. Trap.Event.All is a special event used to denote accepting all events, and normally users should not use this event. Trigger Handler Styles Trap.apex is designed to be able to run in multiple styles. Streamed Style The streamed style adopts streams from Stream.apex to handle trigger logic and make codes more reusable and declarative. Normal Style The normal style is like many other trigger frameworks that provide trigger event handling methods for you to override. Integration Style The integration style enables you to use the streamed style inside an existing trigger framework. Streamed Style The streamed style is distinct by using streams. public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void setUpBeforeInsert(Trap.BulkObject bulkObj) { bulkObj.newStream .filter(new CustomFilterFunc()) .subscribe(Trap.F.addError.apply('test error')); } } Methods are below: | Method | Description | | ------ | ----------- | | setUpBeforeInsert(Trap.BulkObject) | Set up before insert | | setUpBeforeUpdate(Trap.BulkObject) | Set up before update | | setUpBeforeDelete(Trap.BulkObject) | Set up before delete | | setUpAfterInsert(Trap.BulkObject) | Set up after insert | | setUpAfterUpdate(Trap.BulkObject) | Set up after update | | setUpAfterDelete(Trap.BulkObject) | Set up after delete | | setUpAfterUndelete(Trap.BulkObject) | Set up after undelete | | setUp(Trap.BulkObject) | Set up all events | We can use setUp to catch all events. public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void setUp(Trap.BulkObject bulkObj) { bulkObj.newStream .filter((Func)R.anyPass.run( Trap.F.isBeforeInsert, Trap.F.isBeforeUpdate )) .subscribe(Trap.F.addError.apply('Cannot do this')); } } Normal Style The normal style works just like other trigger frameworks. public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void bulkBefore() { // Custom code } public override void beforeInsert(SObject newSO) { Trap.Context triggerContext = this.getTriggerContext(); // Custom code } } Methods are below: | Method | Description | | ------ | ----------- | | bulkBefore() | Called before the before events | | buldAfter() | Called before the after events | | beforeInsert(SObject) | Called on before insert | | beforeUpdate(SObject, SObject) | Called on before update | | beforeDelete(SObject) | Called on before delete | | afterInsert(SObject) | Called on after insert | | afterUpdate(SObject, SObject) | Called on after update | | afterDelete(SObject) | Called on after delete | | afterUndelete(SObject) | Called on after undelete | Integration Style The integration style enables you to use Trap.apex inside your existing trigger framework. public with sharing class CaseTrigger extends Other.TriggerHandler { private Trap.TriggerHandler handler = new Trap.TriggerHandler(); public CaseTrigger() { handler.onBeforeInsert().newStream .tap(R.debug.apply('before insert')) .subscribe(Trap.F.addError.apply('test error')); } public override void bulkBefore() { handler.run(); } } Methods are below: | Method | Description | | ------ | ----------- | | Trap.BulkObject onBeforeInsert() | Get the before insert bulk object | | Trap.BulkObject onBeforeUpdate() | Get the before update bulk object | | Trap.BulkObject onBeforeDelete() | Get the before delete bulk object | | Trap.BulkObject onAfterInsert() | Get the after insert bulk object | | Trap.BulkObject onAfterUpdate() | Get the after update bulk object | | Trap.BulkObject onAfterDelete() | Get the after delete bulk object | | Trap.BulkObject onAfterUndelete() | Get the after undelete bulk object | | Trap.BulkObject onEvent() | Get the bulk object for all events | ","srcFilePath":"src/pages/docs/Trigger_Handler/index.md","id":"Trigger_Handler","location":"/docs/Trigger_Handler/","url":"/trap-apex/docs/Trigger_Handler/"}},"childIds":["Trigger_Handler","Trigger_Execution","Trigger_Controller","search"]},"tutorials":{"title":"Tutorials","description":"The tutorials","url":"/trap-apex/tutorials/getting_started/step_1.html","layout":false,"content":" ","srcFilePath":"src/pages/tutorials/index.soy","id":"tutorials","location":"/tutorials/","customURL":true,"children":{"getting_started":{"title":"Getting Started","description":"The Getting Started Tutorial","tutorialTitle":"Getting started with Trap.apex","url":"/trap-apex/tutorials/getting_started/step_1.html","layout":false,"content":" ","srcFilePath":"src/pages/tutorials/getting_started/index.soy","id":"getting_started","location":"/tutorials/getting_started/","customURL":true,"children":{"step_1":{"title":"Installation","description":"Include Apex files","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":1,"content":" {$page.title} Trap.apex has a dependency on R.apex and Stream.apex. First, we will include R.apex. Include Func.cls, R.cls, and RTest.cls(optional) into your Org, and R.apex is ready. Then, we will include Stream.apex. Include Stream.cls, and StreamTest.cls(optional) into your Org, and Stream.apex is ready. Then, we will include Trap.apex. Include Trap.cls, TrapTest.cls(optional) and other optional files into your Org, and you are ready to go. ","srcFilePath":"src/pages/tutorials/getting_started/step_1.md","id":"step_1","location":"/tutorials/getting_started/step_1.html","url":"/trap-apex/tutorials/getting_started/step_1.html"},"step_10":{"title":"Normal Trigger Event Handler","description":"Normal Trigger Event Handler","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":10,"content":" {$page.title} If you do not want to use the stream style Trap.apex, you can still use a downgraded style, which resembles the normal trigger event handlers. public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void bulkBefore() { // Custom code } public override void beforeInsert(SObject newSO) { Trap.Context triggerContext = this.getTriggerContext(); // Custom code } } See, put your code in bulkBefore to query data, and specific logic in the beforeInsert. ","srcFilePath":"src/pages/tutorials/getting_started/step_10.md","id":"step_10","location":"/tutorials/getting_started/step_10.html","url":"/trap-apex/tutorials/getting_started/step_10.html"},"step_11":{"title":"Trigger Context","description":"Trigger Context","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":11,"content":" {$page.title} It is worthy mentioning that Trap.apex uses its own trigger context, independently from Salesforce trigger context. This brings the benefit that you can test your genuine trigger code completely ignorant of the real trigger context. Most of the time, trigger contexts are invisible to you and you do not need to notice them. However, bear it in mind that you SHOULD NOT use anything like Trigger.xxx from Salesforce trigger context. Use Trap.apex trigger context instead. Trap.Context triggerContext = this.getTriggerContext(); Map newMap = triggerContext.newMap; List newList = triggerContext.newList; ","srcFilePath":"src/pages/tutorials/getting_started/step_11.md","id":"step_11","location":"/tutorials/getting_started/step_11.html","url":"/trap-apex/tutorials/getting_started/step_11.html"},"step_12":{"title":"Unit Test","description":"Unit Test","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":12,"content":" {$page.title} Unit testing with Trap.apex is easy. Here is how you do it in the unit test. @isTest private static void contextTest() { // test code Trap.getInstance().start(Trap.Event.BeforeInsert, null, new List{ new Case() }); // test code } Pass the trigger event, old list and new list into the Trap.start or TriggerHandler.run, and the same code will be executed completely from the data you passed in, not from Salesforce trigger context. ","srcFilePath":"src/pages/tutorials/getting_started/step_12.md","id":"step_12","location":"/tutorials/getting_started/step_12.html","url":"/trap-apex/tutorials/getting_started/step_12.html"},"step_13":{"title":"Take Only Trigger Execution","description":"Take Only Trigger Execution","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":13,"content":" {$page.title} If you have already used a trigger framework, you don't really need to switch to Trap.apex. Most trigger frameworks address the common concerns in developing triggers quite well, and it is not really necessary to do the switch, unless there is some feature you really want. Trap.apex is designed for better integration with existing trigger frameworks. In essence, Trap.apex splits trigger handler management and trigger execution quite clearly. While many trigger frameworks focus a lot on trgger management, Trap.apex excels at its unique stream-based trigger execution powered by Funcs. It is easy for you to integrate only trigger execution from Trap.apex with your existing trigger frameworks. public with sharing class CaseTrigger extends Other.TriggerHandler { private Trap.TriggerHandler handler = new Trap.TriggerHandler(); public CaseTrigger() { handler.onBeforeInsert().newStream .tap(R.debug.apply('before insert')) .subscribe(Trap.F.addError.apply('test error')); } public override void beforeInsert(SObject newSO) { handler.run(); } } We set up the trigger execution logic in the constructor, and run the trigger handler in the specific trigger event handlers like this. ","srcFilePath":"src/pages/tutorials/getting_started/step_13.md","id":"step_13","location":"/tutorials/getting_started/step_13.html","url":"/trap-apex/tutorials/getting_started/step_13.html"},"step_2":{"title":"Preliminary Knowledge","description":"Preliminary Knowledge","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":2,"content":" {$page.title} It's recommended that you have a fair amount of knowledge on R.apex, but it's not required. Trap.apex uses Func objects from R.apex, and a Func is actually a custom Apex object that mimics the behavior of a function. Here is how your implement a custom Func. public class HelloWorldFunc extends Func { public HelloWorldFunc() { super(0); // specify the number of arguments the Func takes } // Provide custom implementation for a Func that takes 0 arguments. public override Object exec() { return 'Hello World'; } } And then you instantiate, and invoke it. Func helloworld = new HelloWorldFunc(); String msg = (String)helloworld.run(); To get deeper with Func objects, please check R.apex. Also Trap.apex heavily relies on Stream.apex. It is best that you can try Stream.apex first. ","srcFilePath":"src/pages/tutorials/getting_started/step_2.md","id":"step_2","location":"/tutorials/getting_started/step_2.html","url":"/trap-apex/tutorials/getting_started/step_2.html"},"step_3":{"title":"Create Triggers","description":"Create Triggers","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":3,"content":" {$page.title} In this tutorial, we will create a trigger for Case objects. Here is how we create the trigger. trigger CaseTrigger on Case ( before insert, before update, before delete, after insert, after update, after delete, after undelete ) { Trap.getInstance().start(); // Start the trap } In whatever trigger it is, you need only one line. Trap.apex takes care of the rest. ","srcFilePath":"src/pages/tutorials/getting_started/step_3.md","id":"step_3","location":"/tutorials/getting_started/step_3.html","url":"/trap-apex/tutorials/getting_started/step_3.html"},"step_4":{"title":"Trigger Handler","description":"Trigger Handler","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":4,"content":" {$page.title} The next step is to create your trigger handler, which encapsulates logic separately from the trigger. public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void setUpBeforeInsert(Trap.BulkObject bulkObj) { bulkObj.newStream .filter(new CustomFilterFunc()) .subscribe(Trap.F.addError.apply('test error')); } } Compared with other trigger frameworks, it is similar that we have separate trigger events to process the business logic. Different is that we set up the streams in the bulk object to do the process. For more information, please check out Stream.apex. The trigger handler is located by naming convertion, adding 'Trigger' to the name of the SObject type. You can configure this behavior, though. ","srcFilePath":"src/pages/tutorials/getting_started/step_4.md","id":"step_4","location":"/tutorials/getting_started/step_4.html","url":"/trap-apex/tutorials/getting_started/step_4.html"},"step_5":{"title":"Bulk Object","description":"Bulk Object","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":5,"content":" {$page.title} A bulk object represents a trunk of SObjects to be processed in the trigger. Bulk objects define the scope that you can work on the data. By default, they provide data as encapsulated streams, newStream for new SObjects and oldStream for old SObjects. bulkObj.oldStream .tap(R.debug.apply('Old objects: ')) .subscribe(new CustomFunc()); You can access to the old/new SObject if you are already in one stream. bulkObj.newStream .filter((Func)R.pipe.run( Trap.F.getOld, new CustomFilterFunc() )) .subscribe(new CustomFunc()); ","srcFilePath":"src/pages/tutorials/getting_started/step_5.md","id":"step_5","location":"/tutorials/getting_started/step_5.html","url":"/trap-apex/tutorials/getting_started/step_5.html"},"step_6":{"title":"Find Specific Objects","description":"Find Specific Objects","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":6,"content":" {$page.title} Trap.apex makes it clear and convenient to do business logic only to some specific objects. For example, here is how we want to prevent changing the Case subject. bulkObj.newStream .filter(Trap.F.changed('Subject')) .subscribe(Trap.F.addError.apply('Cannot modify subject')); With the power of R.apex, it is possible to compose complicated filter logic like: Func changed = (Func)R.anyPass.run( Trap.F.changed('Subject'), Trap.F.changed('OtherField') ); bulkObj.newStream .filter(changed) .subscribe(...); ","srcFilePath":"src/pages/tutorials/getting_started/step_6.md","id":"step_6","location":"/tutorials/getting_started/step_6.html","url":"/trap-apex/tutorials/getting_started/step_6.html"},"step_7":{"title":"Data Sharing","description":"Data Sharing","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":7,"content":" {$page.title} You should not do data query inside a loop in the trigger. So is also true in Trap.apex. Bulk objects manage shared data for you, so that data can be accessed globally in the streams. Here is an example. bulkObj.newStream .tap(bulkObj.data('accounts', new GetAccountsFunc())) .subscribe(bulkObj.provide('accounts', new CustomFunc())); We compute the account list from GetAccountsFunc and set it to the data in the bulk object. Then we provide the data to CustomFunc from the bulk object. Here is how we compute the account list from GetAccountsFunc. public class GetAccountsFunc extends Func { public GetAccountsFunc() { super(1); } public override Object exec(Object arg) { Map newMap = (Map)arg; return [ SELECT Id FROM Account WHERE Id IN :newMap.keySet() ]; } } And we use the account list in our CustomFunc. public class CustomFunc extends Func { public CustomFunc() { super(2); } public override Object exec(Object arg1, Object arg2) { List accountList = (List)arg1; SObject sObj = (SObject)arg2; // Custom code return ...; } } Data set to the bulk object will only be set once, so that it will not cause extra execution for every item through the stream. ","srcFilePath":"src/pages/tutorials/getting_started/step_7.md","id":"step_7","location":"/tutorials/getting_started/step_7.html","url":"/trap-apex/tutorials/getting_started/step_7.html"},"step_8":{"title":"Catch All Events","description":"Catch All Events","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":8,"content":" {$page.title} Sometimes we want to share the same logic when handling multiple events. This comes handy in Trap.apex. public with sharing class CaseTrigger extends Trap.TriggerHandler { public override void setUp(Trap.BulkObject bulkObj) { bulkObj.newStream .filter((Func)R.anyPass.run( Trap.F.isBeforeInsert, Trap.F.isBeforeUpdate )) .subscribe(Trap.F.addError.apply('Cannot do this')); } } Here we prevent any operations when the Case objects are being inserted/updated. ","srcFilePath":"src/pages/tutorials/getting_started/step_8.md","id":"step_8","location":"/tutorials/getting_started/step_8.html","url":"/trap-apex/tutorials/getting_started/step_8.html"},"step_9":{"title":"Trigger Controller","description":"Trigger Controller","buttonTitle":"Done","parentId":"getting_started","layout":"tutorial","time":90,"weight":9,"content":" {$page.title} Trap instance actually is a controller for the trigger handlers created by it. The code below shows the relationship: TriggerHandler handler = Trap.getInstance().find('Case'); Here the Trap controller creates the trigger handler for Case if it is not found, and returns it. Besides creating the trigger handlers, the trigger controller provides much more features. Trap.getInstance() .setEnabled('Case', true) // enable the CaseTrigger .setMaxReEntry('Case', 3) // re-entered for 3 times at most .setUsingNewTransaction('Case', true); // new transaction in trigger ","srcFilePath":"src/pages/tutorials/getting_started/step_9.md","id":"step_9","location":"/tutorials/getting_started/step_9.html","url":"/trap-apex/tutorials/getting_started/step_9.html"}},"childIds":["step_1","step_2","step_3","step_4","step_5","step_6","step_7","step_8","step_9","step_10","step_11","step_12","step_13"]}},"childIds":["getting_started"]}},"childIds":["docs","tutorials"]},"basePath":"/trap-apex"}