Thursday, March 11, 2010

Apex Describes and class versions

I'm resurrecting this blog for posts related to Apex, as using my java.net blog for that purpose would be inappropriate. I'm not sure how often I'll post. When things of general interest crop up while I'm working on the Apex compiler, I suppose.

Today's topic came up as part of a customer support escalation. It's a tricky gotcha with the Apex getMap() function, used for getting all the fields for an sObject type (kind of like reflection for your database objects).

For example, you can get a map of all the fields in the User object by writing:

Map userFields = SObjectType.User.fields.getMap();

With this map, you can see which fields the running user has access to:

boolean b = userFields.get('accountid').getDescribe().isAccessible();

There's a subtle gotcha with using describes, and a fairly simple rule of thumb:

Don't pass the map around between classes.

In my example, I'm getting the AccountId field. This field was added in version 17. Let's say you have two classes, QueryBuilder and DescribeUtil.

// version 16
public class QueryBuilder {
  public String buildQuery {
    Map userFields = DescribeUtil.getUserFields();
    String fieldlist = '';
    for (String key : userFields.keySet()) {
      if (userFields.get(key).getDescribe().isAccessible())
        fieldlist = fieldlist + ',' + key;
    }
  }
}

// version 17
public class DescribeUtil {
  public static Map getUseFields() {
    return SObjectType.User.fields.getMap();
  }
}

You will get an error:

Field User.AccountId is inaccessible in this context

Why? Because QueryBuilder is running in a world where the AccountId field doesn't exist yet! If the getMap() call were done in QueryBuilder, it wouldn't be in there.

So, do yourselves a favor and don't pass these maps around from class to class. The version mismatch is an easy thing to miss.



No comments: