Search

Saturday, 13 June 2015

Intro to Unit Test Data Creation Framework continued…….



In my last blog http://stevefouracre.blogspot.co.uk/2015/06/intro-to-unit-test-data-creation.html I gave examples of how the framework can be used. I also introduced rapidProcessing. Now we will expand the framework making use of rapidProcessing.

Reminder:
            rapidProcessing allows you to bypass the code in triggers, allowing the test            data to be created much faster ( this can also be useful when you are migrating          data into Salesforce ). In both of these situations you are telling Salesforce         exactly what data to create and you don't want the system to manipulate the          data further or to perform any actions within the triggers that may do all kind    of things such as creating additional business process data like Tasks, Events           and Cases etc, or sending out emails to customers etc ( however the latter wont             happen in unit tests as emails are not sent from unit tests ).

Lets take our previous example which will bypass both the Account and Contact triggers:


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TriggerController.rapidProcessing = new Map<System.Type, Boolean>{ Account.class => true, Contact.class => true};

//now rapidProcessing has been turned on for both objects the code in the triggers will be bypassed. You will need to build into your triggers the Trigger Control Framework:

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertAccountAndContacts(keyMap);



Ok for the above example for triggers to be bypassed we need to first create 2 Hierarchical custom settings:

Triggers_Off__c
            This custom settings needs to have the following fields:
           
Field Name
Data Type
value
Boolean


Trigger_Per_Object__c
            This custom settings needs to have the following fields:

Field Name
Data Type
Account
Boolean
Contact
Boolean

            For each additional trigger you create you will need to create an additional             field in this custom setting for that trigger and you will need to add a new else if {} statement to the globalTriggerControlSetting() function in the       TriggerController class. An example of this will be shown next for the        Account and Contact

The 2 custom settings above can be used to bypass the triggers typically when either you are making a deployment to Production or if you are performing a data migration. They will allow you to disable individual or all triggers per person, per Profile or the entire system.


In the TriggerController class we created a number of variables to bypass the Account trigger, now create a similar set of variables for the Contact trigger. We also need to add 2 functions into the class globalTriggerControlSetting() and globalTriggerPerObjectControlSetting():



            //Contact - Only for testing to check if the code ran or not
            public static boolean Contact_Update_Succeeded = false;
            public static boolean Contact_Insert_Succeeded = false;
            public static boolean Contact_Delete_Succeeded = false;
            public static boolean Contact_UnDelete_Succeeded = false;

            //Contact - Disable / Enable parts of trigger
            public static boolean Contact_DisableAllTypes = false;
            public static boolean Contact_DisableInsert = false;
            public static boolean Contact_DisableUpdate = false;
            public static boolean Contact_DisableDelete = false;
            public static boolean Contact_DisableUnDelete = false;

public static boolean globalTriggerControlSetting(){
            return (((Triggers_Off__c.getOrgDefaults() != null) ? Triggers_Off__c.getOrgDefaults().value__c : false) || Triggers_Off__c.getInstance(UserInfo.getUserId()).value__c  || Triggers_Off__c.getInstance(UserInfo.getProfileId()).value__c) ;
}

public static boolean globalTriggerPerObjectControlSetting(String obj){
Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()));
            if (obj == 'Account__c') return (((Trigger_Per_Object__c.getOrgDefaults() != null) ? (boolean)Trigger_Per_Object__c.getOrgDefaults().Account__c  : false) || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getUserId()).Account__c || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()).Account__c) ;
            else if (obj == 'Contact__c') return (((Trigger_Per_Object__c.getOrgDefaults() != null) ? (boolean)Trigger_Per_Object__c.getOrgDefaults().Contact__c  : false) || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getUserId()).Contact__c || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()).Contact__c) ;
            else return false;
}



The Disable variables eg: Contact_DisableAllTypes allows you to disable all of a trigger or individual parts of a trigger, commonly will be used within the body of the code including unit tests. We could have used the custom settings but that would involve using DMLs to turn the triggers on and off.



In the next blog we will create the code for the trigger.

Friday, 5 June 2015

Intro to Unit Test Data Creation Framework continued…….





In my last blog I laid out the codebase required for the framework.

Now you have created the structure of the framework, lets run some examples.
But before we do I'd like to point out 1 thing, in the framework you have various options depending how you personally feel you would like to structure the framework. You can make the TestDataComplexData class extend the TestDataBulkData class instead of the TestDataInsertData and you may want to rename the TestDataComplexData class to something like TestDataCreation class and so it is more generic. These are just options for you to play around with.


To perform no DML and just return a new Contact using the standard json string you can add a returnContact function in the TestDataReturnData class:


The new function will look like:

    public Contact returnContact(KeyValue[] kVals){
            
        return (Contact) (super.returnAnyObject(new TestDataFramework_JsonLibrary.Standard().M.get('CONTACT'), kVals, Contact.class)[0]);
    }



An example of using this new function:


TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.returnContact(null);



To simply create a new Contact using the standard json string:


TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertContact(null, null);



To add some values into certain fields:


KeyValue[] kvs = new KeyValue[]{};
//this will overwrite the Email field with a new email
kvs.add(new KeyValue('Email', 'myemail@yahoo.com', 'String'));

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertContact(null, kvs);



To simply create a new Account and Contact using the standard json string and to add a value into a field:

Map<System.Type, List<KeyValue>> keyMap = new Map<System.Type, List<KeyValue>>();
KeyValue[] kvs = new KeyValue[]{};
//this will overwrite the Email field with a new email
kvs.add(new KeyValue('Email', 'myemail@yahoo.com', 'String'));

kMaps.put(Contact.class, kvs);

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertContactAndAccount(kMaps);



To create 1 Account and 5 Contacts linked using the standard json string#


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TestDataBulkData dataCl = new TestDataBulkData ();
dataCl.insertAccountAndContacts(keyMap);



If your framework is setup so that TestDataComplexData class extends the TestDataBulkData class instead just change the code above to


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertAccountAndContacts(keyMap);



If you now want to bypass the triggers to speed up processing time because you want to upload a lot of records to be used in your testmethod:


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TriggerController.rapidProcessing = new Map<System.Type, Boolean>{ Account.class => true, Contact.class => true};

//now rapidProcessing has been turned on for both objects the code in the triggers will be bypassed. You will need to build into your triggers the Trigger Control Framework which will be the next thing I will cover in my next post:

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertAccountAndContacts(keyMap);



If you want to insert an Account using the standard json string and then update that Account:


TestDataUpdateData dataCl = new TestDataUpdateData();
KeyValue[] kvsUpdate = new KeyValue[]{};
//this will overwrite the BillingPostalCode field with a new post code
kvsUpdate.add(new KeyValue('BillingPostalCode', 'EC1 2CV', 'String'));

//first argument is null because this overrides the fields for the insert part
dataCl.updateAccount(null, kvsUpdate);


If you want to insert an Account using the standard json string and then update that Account:


TestDataUpdateData dataCl = new TestDataUpdateData();

KeyValue[] kvsInsert = new KeyValue[]{};
//this will overwrite the BillingPostalCode field with a new post code
kvsInsert.add(new KeyValue('BillingPostalCode', 'SE1 2SG', 'String'));

KeyValue[] kvsUpdate = new KeyValue[]{};
//this will overwrite the BillingPostalCode field with a new post code
kvsUpdate.add(new KeyValue('BillingPostalCode', 'EC1 2CV', 'String'));

dataCl.updateAccount(kvsInsert, kvsUpdate);



If you want to create a new insert function and add this to the framework, here are the steps you will need to follow:


  1. Create a new constant in the Constant class
  2. Add a new json string to the libraryMap variable in the json library class
  3. Create a new insert function in the TestDataInsertData class similar to the insertContact() function just replacing with the new Sobject type
  4. You can now use this function in the other classes TestDataComplexData, TestDataBulkData and TestDataUpdateData to build more complex data structures if you require



Saturday, 30 May 2015

Intro to Unit Test Data Creation Framework continued.......


Read the first blog at http://stevefouracre.blogspot.co.uk/2015/05/unit-test-data-creation-framework.html

In my last blog I introduced my Unit Test Data Creation Framework, now we will start building the classes of the framework.


First create the Constants class


public class Constants {

public static final String CONST_Account = 'ACCOUNT';
public static final String CONST_Contact = 'CONTACT';

}




Now, in the ITestData class add



public interface ITestData {
    List<sObject> returnAnyObject(String jsonStr, KeyValue[] kVals);                                                 
}




Next, in the TestDataJsonLibrary class add



public class TestDataJsonLibrary {

public static String referenceKey = 'ReferenceID';
public class Standard{

public final Map<String, String> libraryMap = new Map<String, String>{
Constant.CONST_Account                            => '{"attributes":{"type":"Account"},"Field1__c":"Value 1","Field2__c":"Value 2"}',
Constant.CONST_Contact     => '[{"attributes":{"type":"Contact"},"'+referenceKey+'":"Reference Value","Field1__c":"Value 1"}
};

}

}





In the Return Data class add



public abstract class TestDataReturnData implements ITestData{

public Boolean bulkModeOn = false;

public Map<System.Type, String> overrideJson = new Map<System.Type, String>();

    public List<sObject> returnAnyObject(String jsonStr, KeyValue[] kVals){

        List<sObject> sobj;
       
        if(jsonStr.contains(TestDataJsonLibrary.referenceKey))
                                    jsonStr = getFilteredJsonString(jsonStr, kVals);
       
        if(jsonStr.startsWith('['))
            sobj = (List<sObject>) System.Json.deserialize(jsonStr, List<sObject>.class);
        else
            sobj = (List<sObject>) System.Json.deserialize('['+jsonStr+']', List<sObject>.class);
       
        if(kVals != null){
                    for(sObject obj : sobj)
                       obj = UtilDML.setObjData(obj, kVals);
        }
       
        return sobj;
    }


    private String deserialJson(String jsonStr, KeyValue[] kVals){   
            List<Object> deserialLst = (List<Object>) JSON.deserializeUntyped(jsonStr.unescapeEcmaScript());
                       
            String aReferenceKey;
       
            //when setting the fields from the KeyValues if 1 is the lookup field set to  aReferenceKey          
            for(KeyValue kv : kVals){                
                        if(kv.key == TestDataJsonLibrary.referenceKey){                           
                                    aReferenceKey = kv.value;
                                    break;
                        }
            }
           
            List<sObject> serialLst = new List<sObject>();
           
            for(Object obj : deserialLst){
                        Map<String, Object> objMap = (Map<String, Object>) obj;

                        if(aReferenceKey == objMap.get(TestDataJsonLibrary.referenceKey)){                                       
                                    objMap.remove(TestDataJsonLibrary.referenceKey);
                                    serialLst.add(UtilDML.convertToSobject(objMap));
                        }
            }
           
            return JSON.serialize(serialLst);
    }

}





In the Insert Data class add



public virtual class TestDataInsertData extends TestDataReturnData{

    private sObject insertAnyObject(String jsonStr, KeyValue[] kVals, System.Type objType){
        if(overrideJson != null && overrideJson.containsKey(objType))
            jsonStr = overrideJson.get(objType);
       
        sObject sobj = super.returnAnyObject(jsonStr, kVals)[0];
       
        // set to true if inserting multiple records
        if(bulkModeOn == false)
            insert sobj;
              
        return sobj;
    }

    public Contact insertContact(String jsonstr, KeyValue[] kVals){       
        return (Contact) insertAnyObject((jsonstr != null && jsonstr != '') ? jsonstr : new TestDataFramework_JsonLibrary.Standard().M.get(Constants.CONST_Contact), kVals, Contact.class);
    }

}




In the Update Data class add



public virtual class TestDataUpdateData extends TestDataInsertData{

    public Account updateAccount(KeyValue[] insertkVals, KeyValue[] updatekVals){      
        Account acc = super.insertAccount(insertkVals);
       
        if(updatekVals != null)
            acc = (Account) UtilDML.setObjData(acc, updatekVals);
       
        update acc;
       
        return acc;
    }

}





In the ComplexData class add



public virtual class TestDataComplexData extends TestDataInsertData{
    public Account acc{get;set;}
    public Contact cont{get;set;}
    public Opportunity opp{get;set;}


    public Account insertContactAndAccount(Map<System.Type, List<KeyValue>> keyMap){
        //inserts just 1 Account and 1 Contact and links them together, using the map in the argument means you only need to use 1 argument

        //stops a null exception occurring later in the code
        if(keyMap == null){
            keyMap = new Map<System.Type, List<KeyValue>>();
            kMaps.put(Contact.class, new List<KeyValue>());
        }else if(keyMap.containsKey(Contact.class) == false)
            keyMap.put(Contact.class, new List<KeyValue>());
       
       
        this.acc = super.insertAccount(keyMap.get(Account.class));

        // now provide the Id into the KeyValues to link the Objects together
        keyMap.get(Contact.class).add(new KeyValue('AccountId', this.acc.id, 'ID'));

       
        this.cont = super.insertContact(keyMap.get(Contact.class));
       
        return this.acc;
    }


    public Account insertContactOpportunityAndAccount(Map<System.Type, List<KeyValue>> keyMap){

            insertContactAndAccount(keyMap);

            if(keyMap.containsKey(Opportunity.class) == false)
                        keyMap.put(Opportunity.class, new List<KeyValue>());

        keyMap.get(Opportunity.class).add(new KeyValue('AccountId', this.acc.id, 'ID'));
       
        this.opp = super.insertOpportunity(keyMap.get(Opportunity.class));
       
        return this.acc;
    }

}




In the BulkData class add



public virtual class TestDataBulkData extends TestDataInsertData{

    public Account insertAccountAndContacts(Map<System.Type, KeyValueBulk> keyMap){
        //This inserts 1 Account and a number of Contacts

        //stops a null exception occurring later in the code
        if(keyMap == null){
            keyMap = new Map<System.Type, KeyValueBulk>();
            keyMap.put(Contact.class, new KeyValueBulk());
        }else if(kMaps.containsKey(Contact.class) == false)
            kMaps.put(Contact.class, new KeyValueBulk());
       
        this.conts = new List<Case>();
        Account acc = super.insertAccount(keyMap.get(Account.class) .keyValueBulkLst);
       
        bulkModeOn = true; // stops records being inserted
       
                    //link records together
                    (keyMap.get(Contact.class)).keyValueBulkLst.add(new KeyValue('AccountId', acc.id, 'ID'));
                    List<KeyValueBulk> kVals = (keyMap.get(Contact.class)).keyValueBulkLst;
                   
                    for(Integer i = 0; i < (keyMap.get(Contact.class)).insertRecs; i++)
                        this.conts.add(super.insertContact(kVals));
                   
                    insert this.conts;
       
        bulkModeOn = false; //resets flag
       
        return this.acc;
    }
}




KeyValue class looks like this



public class KeyValue{

            public String key{get; set;}
            public String value{get; set;}
            public String fieldType{get; set;}

            public KeyValue(String key, String value, String fieldType){
                       
                        this.key = key;
                        this.value = value;
                        this.fieldType = fieldType.toUpperCase();
            }

            public KeyValue(String key, String value){
                        this.key = key;
                        this.value = value;
            }

            public KeyValue(){
            }
}


KeyValueBulk class looks like this



public class KeyValueBulk{

public integer insertRecs;
public KeyValue[] keyValueBulkLst;

public KeyValueBulk(integer insRecs, KeyValue[] kys){
            insertRecs  = (insertRecs != null && insertRecs > 0) ? insertRecs : 1;
            keyValueBulkLst = kys;
}

}



Also if you have any triggers, for rapid transactional processing create the TriggerController class

To bypass code in triggers which often fire workflows and process builders, which in turn fire triggers again; all of which takes extra processing time. But when you are simply creating test data you are implicitly telling the system to create an exact data set and if you are not actually testing the trigger there is no need when you are creating the test data to run through the code in the trigger.

In the TriggerController class add




public class TriggerController {

            //used specifically in unit test data framework
            public static Map<System.Type, Boolean> rapidProcessing;

            //Account - Disable / Enable parts of trigger
            public static boolean Account_DisableAllTypes = false;
            public static boolean Account_DisableInsert = false;
            public static boolean Account_DisableUpdate = false;
            public static boolean Account_DisableDelete = false;
            public static boolean Account_DisableUnDelete = false;

            //used to unit test the trigger control to ensure the correct parts of the trigger were triggered
            public static boolean Account_Insert_Succeeded = false;
            public static boolean Account_Update_Succeeded = false;
            public static boolean Account_Delete_Succeeded = false;
            public static boolean Account_UnDelete_Succeeded = false;

}



In the UtilDML class add



public class UtilDML {

            public static Sobject setObjData(Sobject aobj, KeyValue[] kVals){
                        Sobject thisobj;
        if (kVals != null){
            for (KeyValue eachval : kVals){
                        try{
                                    thisobj = setFieldVal(aobj, eachval);
                        }
                        catch(Exception ex){system.debug('## ex ' + ex); }
            }
        }
       
        return thisobj;
            }

            public static Sobject setFieldVal(Sobject obj, KeyValue thisKeyVal){
                            
                        system.debug('## thisKeyVal ' + thisKeyVal);
                        if (thisKeyVal.fieldtype == 'DATE'){
                                    String tmpDt = thisKeyVal.value;
                                    Date dt = Date.valueOf(tmpDt.substring(0,9).trim());
                                    system.debug('## dt ' + dt);
                                    obj.put(thisKeyVal.key, dt);
                        }else if (thisKeyVal.fieldtype == 'DATETIME'){
                                    system.debug('## Datetime.valueOf(tmpDt) ' + Datetime.valueOf(thisKeyVal.value));
                                    obj.put(thisKeyVal.key, Datetime.valueOf(thisKeyVal.value));
                                    system.debug('## obj ' + obj);
                        }else if (thisKeyVal.fieldtype == 'DECIMAL')
                                    obj.put(thisKeyVal.key, decimal.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'INTEGER')
                                    obj.put(thisKeyVal.key, integer.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'LONG')
                                    obj.put(thisKeyVal.key, long.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'DOUBLE')
                                    obj.put(thisKeyVal.key, Double.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'BOOLEAN')
                                    obj.put(thisKeyVal.key, ((thisKeyVal.value).toUpperCase() == 'TRUE') );
                        else if (thisKeyVal.fieldtype == 'BLOB')
                                    obj.put(thisKeyVal.key, Blob.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'ID'){
                                    obj.put(thisKeyVal.key, ((ID)thisKeyVal.value));
                        }else
                                    obj.put(thisKeyVal.key, thisKeyVal.value);//String
                       
                        system.debug('## obj ' + obj);
                        return obj;
                       
            }

 public static sObject convertToSobject(Map<String, Object> objMap){

     sObject sObj = Schema.getGlobalDescribe().get((String)((Map<String, Object>)objMap.get('attributes')).get('type')).newSObject();
     Map<String, Schema.SObjectField> sObjMap = sObj.getSObjectType().getDescribe().fields.getMap();

     for(String key : objMap.keySet()){
    
     if(sObjMap.containskey(key)){
    
     Schema.DescribeFieldResult field = sObjMap.get(key).getDescribe();
    
     String fieldType = field.getType().Name();
     String value = (String) objMap.get(key);
    
if(fieldType == 'DATE'){ 
sObj.put(key, Date.valueOf(value));
}else if(fieldType == 'DATETIME'){
sObj.put(key, Datetime.valueOf(value));
}else if(fieldType == 'DECIMAL'){
//sObj.put(key, Decimal.valueof(value));
sObj.put(key, Decimal.valueOf(value));
}else if(fieldType == 'INTEGER'){
sObj.put(key, Integer.valueOf(value));
}else if(fieldType == 'LONG'){
sObj.put(key, Long.valueOf(value));
}else if(fieldType == 'DOUBLE'){
sObj.put(key, Double.valueOf(value));
}else if(fieldType == 'BOOLEAN'){
sObj.put(key, (value.toUpperCase() == 'TRUE'));
}else{// String
sObj.put(key, value);
}
     }
     }
    
     return sObj;
    }

}



 In my next blog we will start building some examples of using the framework. If you have any questions about the framework please add comments on my blog

Sunday, 24 May 2015

Unit Test Data Creation Framework


Ive worked in so many companies where developers copy and paste code from 1 unit test to another to create test data for the testmethod. Then when they need to add a new field because it is a new required field that is failing tests they realise they've got to copy to all classes.

Some companies improve by having a central class but the functions they make have passed in arguments for each field, so for each field that needs to be passed in requires a change to this class.

Some improve this by passing in a list of a key value pair classes. This will become clearer later when we start coding in my follow on blog to this one.

Some may improve even further by having a for loop to create as many records as you like.

But Ive never seen a company to have all the benefits above plus using json to serialise and de-serialise to / from generic sobjects. Use OOP to provide a logical framework separating functionality that provide only returning Casted Sobjects, inserting single objects, inserting multiple objects for bulk testing, updating objects, inserting multiple objects of different types and linking them together, and inserting multiple objects creating complex linked data structures.

Building into the framework a highly flexible capability so whatever type of test data is required the framework can easily create.

Also to provide an option for rapid transactional processing to speed up unit tests and also speed up deployments.

The Framework can also be used to create data in Salesforce for many purposes such as data migration, exposing for web services etc.

So lets get started.
First create a number of classes

ITestData
            This is the interface class

TestDataJsonLibrary
            Provides the json strings that will be serialised into sobjects

TestDataReturnData
            Serialises json strings from TestDataJsonLibrary or custom json strings

TestDataInsertData
            DML transactions of the serialised json strings occurs here, but only inserts 1 record

TestDataUpdateData
            Inserts and then updates a record

TestDataComplexData
            Inserts multiple records of different Sobjects and links the records together

TestDataBulkData
            Inserts multiple records

UtilDML
            A utility class used to assign values to any fields of any Sobject

KeyValue
            This contains information of fields, their api name, value and data type

KeyValueBulk

            This contains information in KeyValue, how many records to create for each  object and whether or not to bypass code in triggers for rapid transactional  processing ( the default is not to bypass triggers )

TriggerController
            If you have any code in triggers also create this class

In my next blog I will start showing you the coding

Friday, 22 May 2015

Whats good about Salesforce Summer 15 Release

Duplicate Management
Im sure you are now aware of this, if not you need to be it will be invaluable to every organisation.
Maintaining clean and accurate data is one of the most important things you can do to help your organization get the most out of
Salesforce. With Data.com Duplicate Management, you can control whether and when you allow users to create duplicate records
inside Salesforce; customize the logic that’s used to identify duplicates; and create reports on the duplicates you do allow users to
save.

DML Options
runAsCurrentUser()
Set to true to make sure that sharing rules for the current user are enforced when duplicate rules run. Set to false to use
the sharing rules specified in the class for the request. If no sharing rules are specified, Apex code runs in system context and
sharing rules for the current user are not enforced.

Search Namespace
System.Search.find(String) method, which performs dynamic SOSL queries
Example
getSObject()  - Returns an sObject from a SearchResult object.

Generic Model For Daisy Chain Batches



In the Winter 15 release Salesforce released my idea allowing daisy chaining batch jobs, so when 1 batch job finishes a new batch can be started. Now if you have say 3 different batch jobs and at the end of the batch a new batch must be started, or you need a decision making process to decide if to start a new batch or not, you would need to create 3 separate batch classes. So, I've created a design model where only 1 batch class needs to be created, this provides a generic and flexible batch model, as shown below:


global class batchclass implements Database.Batchable<sObject>, Database.Stateful, Database.AllowsCallouts{
    global String batchType;
    global String soql;
    global Boolean success;
   

    //each time you would normally want to create a new batch class instead simply add a new else if statement in execute() and finish()
    global batchclass(){
        //default batch type
        batchType = Constants.CONST_TYPE1;
    }
   
    global batchclass(String thisbatchType){
        //pass the batch type to run
    batchType = thisbatchType;
    }
   
    global batchclass(String thisbatchType, String thissoql){
        //pass the batch type to run and batch requires a soql      
    batchType = thisbatchType;
        soql = thissoql;
    }
   
    global Database.QueryLocator start(Database.BatchableContext bc) {
        //Some batches will need to retrieve data from the database, use soql for this
    if (soql == null || soql == '')
            return Database.getQueryLocator('Select id From User limit 1');//data will not be used in the execute but User is used because there will always be at least 1 user in the org
        else
            return Database.getQueryLocator(soql);
    }
   
    global void execute(Database.BatchableContext BC, List<sObject> glbs){
        //decide which function to call depending on the type
    if (Limits.getLimitCallouts() > (Limits.getCallouts() -10) ) {
            if (batchType == Constants.CONST_TYPE1)//Constants just contains static variable strings
                success = Utils.callFunction1();
            else if (batchType == Constants.CONST_TYPE2)
                success = Utils.callFunction2();
        }
    //success  variable deecides whether the batch will be run again
    }

    global void finish(Database.BatchableContext BC){
        //if success = true  decideToRunFunction1Again is called which will either call a different batch job, or the same. It may contain a decision process as well
    //such as each time the execute() runs a custom setting is incremented and the decide functions look at this custom setting to decide if to run the batch again
    if (batchType == Constants.CONST_TYPE1 && success){
            Utils.decideToRunFunction1Again();
        }
        else if (batchType == Constants.CONST_TYPE2 && success){
            Utils.decideToRunFunction2Again();
        }
    }

}