Menu Bar

Thursday 27 September 2012

Understanding Apex Describe Information

Understanding Apex Describe Information

Apex provides two data structures for sObject and field describe information:
  • Token—a lightweight, serializable reference to an sObject or a field that is validated at compile time.
  • Describe result—an object that contains all the describe properties for the sObject or field. Describe result objects are not serializable, and are validated at runtime.
It is easy to move from a token to its describe result, and vice versa. Both sObject and field tokens have the method getDescribe which returns the describe result for that token. On the describe result, thegetSObjectType and getSObjectField methods return the tokens for sObject and field, respectively.
Because tokens are lightweight, using them can make your code faster and more efficient. For example, use the token version of an sObject or field when you are determining the type of an sObject or field that your code needs to use. The token can be compared using the equality operator (==) to determine whether an sObject is the Account object, for example, or whether a field is the Name field or a custom calculated field.
The following code provides a general example of how to use tokens and describe results to access information about sObject and field properties:
// Create a new account as the generic type sObject 
    
sObject s = new Account();

// Verify that the generic sObject is an Account sObject 
    
System.assert(s.getsObjectType() == Account.sObjectType);

// Get the sObject describe result for the Account object 
    
Schema.DescribeSObjectResult r = Account.sObjectType.getDescribe();

// Get the field describe result for the Name field on the Account object 
    
Schema.DescribeFieldResult f = Schema.sObjectType.Account.fields.Name;

// Verify that the field token is the token for the Name field on an Account object 
    
System.assert(f.getSObjectField() == Account.Name);

// Get the field describe result from the token 
    
f = f.getSObjectField().getDescribe();
The following algorithm shows how you can work with describe information in Apex:
  1. Generate a list or map of tokens for the sObjects in your organization (see Accessing All sObjects.)
  2. Determine the sObject you need to access.
  3. Generate the describe result for the sObject.
  4. If necessary, generate a map of field tokens for the sObject (see Accessing All Field Describe Results for an sObject.)
  5. Generate the describe result for the field the code needs to access.

Understanding Describe Information Permissions

Apex generally runs in system mode. All classes and triggers that are not included in a package, that is, are native to your organization, have no restrictions on the sObjects that they can look up dynamically. This means that with native code, you can generate a map of all the sObjects for your organization, regardless of the current user's permission.
Dynamic Apex, contained in managed packages created by salesforce.com ISV partners that are installed from Force.com AppExchange, have restricted access to any sObject outside the managed package. Partners can set the API Access value within the package to grant access to standard sObjects not included as part of the managed package. While Partners can request access to standard objects, custom objects are not included as part of the managed package and can never be referenced or accessed by dynamic Apex that is packaged.
For more information, see “About API and Dynamic Apex Access in Packages” in the Salesforce online help.

Using sObject Tokens

SObjects, such as Account and MyCustomObject__c, act as static classes with special static methods and member variables for accessing token and describe result information. You must explicitly reference an sObject and field name at compile time to gain access to the describe result.
To access the token for an sObject, use one of the following methods:
  • Access the sObjectType member variable on an sObject type, such as Account.
  • Call the getSObjectType method on an sObject describe result, an sObject variable, a list, or a map.
Schema.SObjectType is the data type for an sObject token.
In the following example, the token for the Account sObject is returned:
Schema.sObjectType t = Account.sObjectType;
The following also returns a token for the Account sObject:
Account A = new Account();
Schema.sObjectType T = A.getSObjectType();
This example can be used to determine whether an sObject or a list of sObjects is of a particular type:
public class sObjectTest {
{
// Create a generic sObject variable s 
    
SObject s = Database.query('SELECT Id FROM Account LIMIT 1');

// Verify if that sObject variable is an Account token 
    
System.assertEquals(s.getSObjectType(), Account.sObjectType);

// Create a list of generic sObjects  
    
List<sObject> l = new Account[]{};

// Verify if the list of sObjects contains Account tokens 
    
System.assertEquals(l.getSObjectType(), Account.sObjectType);
}
}
Some standard sObjects have a field called sObjectType, for example, AssignmentRule, QueueSObject, and RecordType. For these types of sObjects, always use the getSObjectType method for retrieving the token. If you use the property, for example, RecordType.sObjectType, the field is returned.

Using sObject Describe Results

To access the describe result for an sObject, use one of the following methods:
  • Call the getDescribe method on an sObject token.
  • Use the Schema sObjectType static variable with the name of the sObject. For example, Schema.sObjectType.Lead.
Schema.DescribeSObjectResult is the data type for an sObject describe result.
The following example uses the getDescribe method on an sObject token:
Schema.DescribeSObjectResult D = Account.sObjectType.getDescribe();
The following example uses the Schema sObjectType static member variable:
Schema.DescribeSObjectResult D = Schema.SObjectType.Account;
For more information about the methods available with the sObject describe result, see sObject Describe Result Methods.

Using Field Tokens

To access the token for a field, use one of the following methods:
  • Access the static member variable name of an sObject static type, for example, Account.Name.
  • Call the getSObjectField method on a field describe result.
The field token uses the data type Schema.SObjectField.
In the following example, the field token is returned for the Account object's AccountNumber field:
Schema.SObjectField F = Account.AccountNumber;
In the following example, the field token is returned from the field describe result:
// Get the describe result for the Name field on the Account object 
    
Schema.DescribeFieldResult f = Schema.sObjectType.Account.fields.Name;

// Verify that the field token is the token for the Name field on an Account object 
    
System.assert(f.getSObjectField() == Account.Name);

// Get the describe result from the token 
    
f = f.getSObjectField().getDescribe();

Using Field Describe Results

To access the describe result for a field, use one of the following methods:
  • Call the getDescribe method on a field token.
  • Access the fields member variable of an sObject token with a field member variable (such as Name, BillingCity, and so on.)
The field describe result uses the data type Schema.DescribeFieldResult.
The following example uses the getDescribe method:
Schema.DescribeFieldResult F = Account.AccountNumber.getDescribe();
This example uses the fields member variable method:
Schema.DescribeFieldResult F = Schema.SObjectType.Account.fields.Name;
In the example above, the system uses special parsing to validate that the final member variable (Name) is valid for the specified sObject at compile time. When the parser finds the fields member variable, it looks backwards to find the name of the sObject (Account) and validates that the field name following the fields member variable is legitimate. The fields member variable only works when used in this manner.
You can only have 100fields member variable statements in an Apex class or trigger.
Note
You should not use the fields member variable without also using either a field member variable name or the getMap method. For more information on getMap, see Accessing All Field Describe Results for an sObject.
For more information about the methods available with a field describe result, see Describe Field Result Methods.

Accessing All sObjects

Use the Schema getGlobalDescribe method to return a map that represents the relationship between all sObject names (keys) to sObject tokens (values). For example:
Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
The map has the following characteristics:
  • It is dynamic, that is, it is generated at runtime on the sObjects currently available for the organization, based on permissions.
  • The sObject names are case insensitive.
  • The keys use namespaces as required.
  • The keys reflect whether the sObject is a custom object.
For example, if the code block that generates the map is in namespace N1, and an sObject is also in N1, the key in the map is represented as MyObject__c. However, if the code block is in namespace N1, and the sObject is in namespace N2, the key is N2__MyObject__c.
In addition, standard sObjects have no namespace prefix.

Creating sObjects Dynamically

You can create sObjects whose types are determined at run time by calling the newSObject method of the Schema.sObjectType sObject token class. The following example shows how to get an sObject token that corresponds to an sObject type name using the Schema.getGlobalDescribe method. Then, an instance of the sObject is created through the newSObject method ofSchema.sObjectType. This example also contains a test method that verifies the dynamic creation of an account.
public class DynamicSObjectCreation {
    public static sObject createObject(String typeName) {
        Schema.SObjectType targetType = Schema.getGlobalDescribe().get(typeName);
        if (targetType == null) {
            // throw an exception 
    
        }
        
        // Instantiate an sObject with the type passed in as an argument 
    
        //  at run time. 
    
        return targetType.newSObject(); 
    }
        
    static testmethod void testObjectCreation() {
        String typeName = 'Account';
        String acctName = 'Acme';
        
        // Create a new sObject by passing the sObject type as an argument. 
    
        Account a = (Account)createObject(typeName);        
        System.assertEquals(typeName, String.valueOf(a.getSobjectType()));
        // Set the account name and insert the account. 
    
        a.Name = acctName;
        insert a;

        // Verify the new sObject got inserted. 
    
        Account[] b = [SELECT Name from Account WHERE Name = :acctName];
        system.assert(b.size() > 0);
    }
}

Accessing All Field Describe Results for an sObject

Use the field describe result's getMap method to return a map that represents the relationship between all the field names (keys) and the field tokens (values) for an sObject.
The following example generates a map that can be used to access a field by name:
Map<String, Schema.SObjectField> M = Schema.SObjectType.Account.fields.getMap();
Note
The value type of this map is not a field describe result. Using the describe results would take too many system resources. Instead, it is a map of tokens that you can use to find the appropriate field. After you determine the field, generate the describe result for it.
The map has the following characteristics:
  • It is dynamic, that is, it is generated at runtime on the fields for that sObject.
  • All field names are case insensitive.
  • The keys use namespaces as required.
  • The keys reflect whether the field is a custom object.
For example, if the code block that generates the map is in namespace N1, and a field is also in N1, the key in the map is represented as MyField__c. However, if the code block is in namespace N1, and the field is in namespace N2, the key is N2__MyField__c.
In addition, standard fields have no namespace prefix.

Accessing All Data Categories Associated with an sObject

Use the describeDataCategory Groups and describeDataCategory GroupStructures methods to return the categories associated with a specific object:
  1. Return all the category groups associated with the objects of your choice (see describeDataCategory Groups).
  2. From the returned map, get the category group name and sObject name you want to further interrogate (see Schema.Describe​DataCategoryGroupResult).
  3. Specify the category group and associated object, then retrieve the categories available to this object (see describeDataCategory GroupStructures).
The describeDataCategory GroupStructures method returns the categories available for the object in the category group you specified. For additional information about data categories, see “What are Data Categories?” in the Salesforce online help.
In the following example, the describeDataCategoryGroupSample method returns all the category groups associated with the Article and Question objects. ThedescribeDataCategoryGroupStructures method returns all the categories available for articles and questions in the Regions category group. For additional information about articles and questions, see “Managing Articles and Translations” and “Answers Overview” in the Salesforce online help.
To use the following example, you must:
  • Enable Salesforce Knowledge.
  • Enable the answers feature.
  • Create a data category group called Regions.
  • Assign Regions as the data category group to be used by Answers.
  • Make sure the Regions data category group is assigned to Salesforce Knowledge.
For more information on creating data category groups, see “Creating and Modifying Category Groups” in the Salesforce online help. For more information on answers, see “Answers Overview” in the Salesforce online help.
public class DescribeDataCategoryGroupSample {
   public static List<DescribeDataCategoryGroupResult> describeDataCategoryGroupSample(){
      List<DescribeDataCategoryGroupResult> describeCategoryResult;
      try {
         //Creating the list of sobjects to use for the describe 
    
         //call 
    
         List<String> objType = new List<String>();

         objType.add('KnowledgeArticleVersion');
         objType.add('Question');

         //Describe Call 
    
         describeCategoryResult = Schema.describeDataCategoryGroups(objType);
   
         //Using the results and retrieving the information 
    
         for(DescribeDataCategoryGroupResult singleResult : describeCategoryResult){
            //Getting the name of the category 
    
            singleResult.getName();

            //Getting the name of label 
    
            singleResult.getLabel();

            //Getting description 
    
            singleResult.getDescription();

            //Getting the sobject 
    
            singleResult.getSobject();
         }         
      } catch(Exception e){
      }
      
      return describeCategoryResult;
   }
}

public class DescribeDataCategoryGroupStructures {
   public static List<DescribeDataCategoryGroupStructureResult> 
   getDescribeDataCategoryGroupStructureResults(){
      List<DescribeDataCategoryGroupResult> describeCategoryResult;
      List<DescribeDataCategoryGroupStructureResult> describeCategoryStructureResult;
      try {
         //Making the call to the describeDataCategoryGroups to 
    
         //get the list of category groups associated 
    
         List<String> objType = new List<String>();
         objType.add('KnowledgeArticleVersion');
         objType.add('Question');
         describeCategoryResult = Schema.describeDataCategoryGroups(objType);
         
         //Creating a list of pair objects to use as a parameter 
    
         //for the describe call 
    
         List<DataCategoryGroupSobjectTypePair> pairs = 
            new List<DataCategoryGroupSobjectTypePair>();
         
         //Looping throught the first describe result to create 
    
         //the list of pairs for the second describe call 
    
         for(DescribeDataCategoryGroupResult singleResult : 
         describeCategoryResult){
            DataCategoryGroupSobjectTypePair p =
               new DataCategoryGroupSobjectTypePair();
            p.setSobject(singleResult.getSobject());
            p.setDataCategoryGroupName(singleResult.getName());
            pairs.add(p);
         }
         
         //describeDataCategoryGroupStructures() 
    
         describeCategoryStructureResult = 
            Schema.describeDataCategoryGroupStructures(pairs, false);

         //Getting data from the result 
    
         for(DescribeDataCategoryGroupStructureResult singleResult : describeCategoryStructureResult){
            //Get name of the associated Sobject 
    
            singleResult.getSobject();

            //Get the name of the data category group 
    
            singleResult.getName();

            //Get the name of the data category group 
    
            singleResult.getLabel();

            //Get the description of the data category group 
    
            singleResult.getDescription();

            //Get the top level categories 
    
            DataCategory [] toplevelCategories = 
               singleResult.getTopCategories();
            
            //Recursively get all the categories 
    
            List<DataCategory> allCategories = 
               getAllCategories(toplevelCategories);

            for(DataCategory category : allCategories) {
               //Get the name of the category 
    
               category.getName();

               //Get the label of the category 
    
               category.getLabel();

               //Get the list of sub categories in the category 
    
               DataCategory [] childCategories = 
                  category.getChildCategories();
            }
         }
      } catch (Exception e){
      }
      return describeCategoryStructureResult;
    }
    
   private static DataCategory[] getAllCategories(DataCategory [] categories){
      if(categories.isEmpty()){
         return new DataCategory[]{};
      } else {
         DataCategory [] categoriesClone = categories.clone();
         DataCategory category = categoriesClone[0];
         DataCategory[] allCategories = new DataCategory[]{category};
         categoriesClone.remove(0);
         categoriesClone.addAll(category.getChildCategories());
         allCategories.addAll(getAllCategories(categoriesClone));
         return allCategories;
      }
   }
}

Testing Access to All Data Categories Associated with an sObject

The following example tests the describeDataCategoryGroupSample method shown in Accessing All Data Categories Associated with an sObject. It ensures that the returned category group and associated objects are correct.
@isTest
private class DescribeDataCategoryGroupSampleTest {
   public static testMethod void describeDataCategoryGroupSampleTest(){
      List<DescribeDataCategoryGroupResult>describeResult =
                 DescribeDataCategoryGroupSample.describeDataCategoryGroupSample();
      
      //Assuming that you have KnowledgeArticleVersion and Questions 
    
      //associated with only one category group 'Regions'. 
    
      System.assert(describeResult.size() == 2,
           'The results should only contain two results: ' + describeResult.size());
      
      for(DescribeDataCategoryGroupResult result : describeResult) {
         //Storing the results 
    
         String name = result.getName();
         String label = result.getLabel();
         String description = result.getDescription();
         String objectNames = result.getSobject();
         
         //asserting the values to make sure 
    
         System.assert(name == 'Regions',
         'Incorrect name was returned: ' + name);
         System.assert(label == 'Regions of the World',
         'Incorrect label was returned: ' + label);
         System.assert(description == 'This is the category group for all the regions',
         'Incorrect description was returned: ' + description);
         System.assert(objectNames.contains('KnowledgeArticleVersion') 
                       || objectNames.contains('Question'),
                       'Incorrect sObject was returned: ' + objectNames);
      }
   }
}
This example tests the describeDataCategoryGroupStructures method shown in Accessing All Data Categories Associated with an sObject. It ensures that the returned category group, categories and associated objects are correct.
@isTest
private class DescribeDataCategoryGroupStructuresTest {
   public static testMethod void getDescribeDataCategoryGroupStructureResultsTest(){
      List<Schema.DescribeDataCategoryGroupStructureResult> describeResult =
         DescribeDataCategoryGroupStructures.getDescribeDataCategoryGroupStructureResults();
      
      System.assert(describeResult.size() == 2,
            'The results should only contain 2 results: ' + describeResult.size());
            
      //Creating category info 
    
      CategoryInfo world = new CategoryInfo('World', 'World');
      CategoryInfo asia = new CategoryInfo('Asia', 'Asia');
      CategoryInfo northAmerica = new CategoryInfo('NorthAmerica',
                                                  'North America');
      CategoryInfo southAmerica = new CategoryInfo('SouthAmerica',
                                                  'South America');
      CategoryInfo europe = new CategoryInfo('Europe', 'Europe');
      
      List<CategoryInfo> info = new CategoryInfo[] {
        asia, northAmerica, southAmerica, europe
     };
      
      for (Schema.DescribeDataCategoryGroupStructureResult result : describeResult) {
         String name = result.getName();
         String label = result.getLabel();
         String description = result.getDescription();
         String objectNames = result.getSobject();
         
         //asserting the values to make sure 
    
         System.assert(name == 'Regions', 
         'Incorrect name was returned: ' + name);
         System.assert(label == 'Regions of the World',
         'Incorrect label was returned: ' + label);
         System.assert(description == 'This is the category group for all the regions',
         'Incorrect description was returned: ' + description);
         System.assert(objectNames.contains('KnowledgeArticleVersion') 
                    || objectNames.contains('Question'),
                       'Incorrect sObject was returned: ' + objectNames);
         
         DataCategory [] topLevelCategories = result.getTopCategories();
         System.assert(topLevelCategories.size() == 1,
         'Incorrect number of top level categories returned: ' + topLevelCategories.size());
         System.assert(topLevelCategories[0].getLabel() == world.getLabel() &&
                       topLevelCategories[0].getName() == world.getName());
         
         //checking if the correct children are returned 
    
         DataCategory [] children = topLevelCategories[0].getChildCategories();
         System.assert(children.size() == 4,
         'Incorrect number of children returned: ' + children.size());
         for(Integer i=0; i < children.size(); i++){
            System.assert(children[i].getLabel() == info[i].getLabel() &&
                          children[i].getName() == info[i].getName());
         }
      }
      
   }
   
   private class CategoryInfo {      
      private final String name;
      private final String label;
            
      private CategoryInfo(String n, String l){
         this.name = n;
         this.label = l;
      }
      
      public String getName(){
         return this.name;
      }
      
      public String getLabel(){
         return this.label;
      }
   }
}

Trigger Context Variables


Trigger Context Variables

All triggers define implicit variables that allow developers to access runtime context. These variables are contained in the System.Trigger class:
VariableUsage
isExecutingReturns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call.
isInsertReturns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API.
isUpdateReturns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API.
isDeleteReturns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API.
isBeforeReturns true if this trigger was fired before any record was saved.
isAfterReturns true if this trigger was fired after all records were saved.
isUndeleteReturns true if this trigger was fired after a record is recovered from the Recycle Bin (that is, after an undelete operation from the Salesforce user interface, Apex, or theAPI.)
newReturns a list of the new versions of the sObject records.
Note that this sObject list is only available in insert and update triggers, and the records can only be modified in before triggers.
newMapA map of IDs to the new versions of the sObject records.
Note that this map is only available in before updateafter insert, and after update triggers.
oldReturns a list of the old versions of the sObject records.
Note that this sObject list is only available in update and delete triggers.
oldMapA map of IDs to the old versions of the sObject records.
Note that this map is only available in update and delete triggers.
sizeThe total number of records in a trigger invocation, both old and new.
Note
If any record that fires a trigger includes an invalid field value (for example, a formula that divides by zero), that value is set to null in the newnewMapold, and oldMap trigger context variables.
For example, in this simple trigger, Trigger.new is a list of sObjects and can be iterated over in a for loop, or used as a bind variable in the IN clause of a SOQL query:
Trigger t on Account (after insert) {
    for (Account a : Trigger.new) {
        // Iterate over each sObject 
    
    }

    // This single query finds every contact that is associated with any of the 
    
    // triggering accounts. Note that although Trigger.new is a collection of   
    
    // records, when used as a bind variable in a SOQL query, Apex automatically 
    
    // transforms the list of records into a list of corresponding Ids. 
    
    Contact[] cons = [SELECT LastName FROM Contact
                      WHERE AccountId IN :Trigger.new];
}
This trigger uses Boolean context variables like Trigger.isBefore and Trigger.isDelete to define code that only executes for specific trigger conditions:
trigger myAccountTrigger on Account(before delete, before insert, before update, 
                                    after delete, after insert, after update) {
if (Trigger.isBefore) {
    if (Trigger.isDelete) {

        // In a before delete trigger, the trigger accesses the records that will be 
    
        // deleted with the Trigger.old list. 
    
        for (Account a : Trigger.old) {
            if (a.name != 'okToDelete') {
                a.addError('You can\'t delete this record!');
            } 
        }
    } else {

    // In before insert or before update triggers, the trigger accesses the new records 
    
    // with the Trigger.new list. 
    
        for (Account a : Trigger.new) {
            if (a.name == 'bad') {
                a.name.addError('Bad name');
            }
    }
    if (Trigger.isInsert) {
        for (Account a : Trigger.new) {
            System.assertEquals('xxx', a.accountNumber); 
            System.assertEquals('industry', a.industry); 
            System.assertEquals(100, a.numberofemployees);
            System.assertEquals(100.0, a.annualrevenue);
            a.accountNumber = 'yyy';
        }

// If the trigger is not a before trigger, it must be an after trigger. 
    
} else {
    if (Trigger.isInsert) {
        List<Contact> contacts = new List<Contact>();
        for (Account a : Trigger.new) {        
            if(a.Name == 'makeContact') {
                contacts.add(new Contact (LastName = a.Name,
                                          AccountId = a.Id));
            }
        } 
      insert contacts;
    }
  }
}}}