Schema.GlobalDescribe(), Managed Packages and API Versions

, development, Force.com, Salesforce

This is a short write up of a few interesting things I noticed when trying to package some work dealing with dynamic apex in a managed package.

So the background is that I have a set of objects that I want to dynamically get content from - a user can choose a field and the system goes away and retrieves the fields value for them. This system is part of a managed package that is distributed to a number of customers and as such the system should be able to handle the system administrator adding new fields to the system and still be able to retrieve their values. This makes the obvious (and obviously a bad idea) hardcoding a no-no. A further point to note (this will be more useful later) the system currently runs on API v26.

I have replicated this in a simpler format by having an object called Test_Record__c (creative naming at its best!) and the following test class as part of a managed package with namespace pbattisson:

@isTest
public with sharing class SchemaDescribeTests {

  static testMethod void testGetAllContainsTestRecord() {
    Map<string, schema.sobjecttype> describeInfo = Schema.getGlobalDescribe();

    System.assert(describeInfo.containsKey('Test_Record__c'), 'Describe info. does not contain the key for Test_Record__c');
  }

  static testMethod void testCanRetrieveTestRecordFromGlobalDescribe() {
    Map<string, schema.sobjecttype> describeInfo = Schema.getGlobalDescribe();

    System.assertNotEquals(null, describeInfo.get('Test_Record__c'), 'Describe info. does not retrieve Test_Record__c properly');
  }

  static testMethod void testRetrieveAPINameFromObjectCorrectly() {
    String objectName = Schema.SObjectType.Test_Record__c.getName();
    System.assertEquals('pbattisson__Test_Record__c', objectName, 'Describe returns different name');
  }

  static testMethod void testRetrieveAPINameFromGlobalDescribe() {
    String objectName = Schema.SObjectType.Test_Record__c.getName();
    Map<string, schema.sobjecttype> describeInfo = Schema.getGlobalDescribe();

    System.assertNotEquals(null, describeInfo.get(objectName), 'Schema describe returns null given SObject name token');
  }
}

So if you package and run this in API v26 or v27 then it all passes fine and runs wonderfully :-) describe tests If you however change the api version to 28 then it starts failing: describe tests

Looking at the Salesforce documentation then apparently the keys are always case-insensitive. There has been an upgrade in v28 that means that an namespace for the object is always returned whatever context the code is run in. This is an important change for developers to note as it means that you have to be careful if you have two classes running in different API version communicating. For example if my code to retrieve the name was v26 or v27 of the API and the code to get the object type from the global describe was in v28 then it would error if I forgot to include the namespace. (I have another issue to mention in another blog post that will highlight this same problem in a different context)

This is a key thing for ISVs to be aware of and ensure they code defensively against - as well as ensuring that any of their integration partners are aware of. You could have customers with broken systems just because they have written code against a different API version.

So morals of the story - read the release notes super carefully and try to match API versions when using newer more dynamic features.

Note there is a good Stack Exchange thread from the awesome Matt Lacey that discusses this (and includes my brief findings).

Share on Twitter, Facebook, Google+
Prev Next