Monday 7 February 2011

Describe, and Schema, and SObject and Oh my!

I've seen a few of the cool things that can be done with "describe" information in Apex, and using the Schema and SObjects to handle different objects in a generic fashion, but I only recently had a good opportunity to get my hands on it. It was an interesting exercise, so I thought I'd write up my experiences.

First: the requirement. We wanted to give limited visibility of accounts in some circumstances, and full visibility in others. If a user owns an account or is a member of the account team, he (or she) should see all details; otherwise they should only see the account name and owner.

Sharing rules, field level security, record types and page layouts were options that wouldn't deliver what we wanted: sharing rules would control whether a user could see a record or not, not what they could see on the object. Field level security would limit access to fields for all accounts, not on an account-by-account basis. Record types and page layouts would not work for a reason that would take some time to explain, but seasoned admins/developers can work out for themselves.

So we had to customise – we could have overridden view and edit actions with Visualforce, and some of the upcoming Spring '11 features would take some of the pain away when it comes to controlling what fields display on the page in a config-friendly, no-code-changes-needed way. Personally though, I don't like overriding standard functionality with something that looks, feels and operates in such a similar way.

Instead, we decided to create another object that held only the details that anyone can see (account name and owner) that would be created, updated or deleted when something of interest happened on the account object. The oragnisation wide default for accounts could be set to private, granting access to owners and people above them in the hierarchy (and people added to the account team), whereas the new custom object would be public for all. Search for an account and you’ll either see the new custom object if an account exists, or the custom object and the account (if you are able to see the account).

So why bother with the Describe and Schema objects? We also needed to do the same thing for contacts, and possibly other objects in the future. We could have written specific code for each object but it seemed a shame when the operations were the same over the different objects:

  • If an account/contact is inserted, create a corresponding custom object record with the necessary details.
  • If an account/contact is updated, update the corresponding record.
  • If an account/contact is deleted, delete the corresponding record.

So I created three methods, one for each of those scenarios. Each method took a list (or Map) of SObjects and created/edited/deleted a corresponding record using Schema, SObject and Describe. This was then mixed in with some variables to hold information about what fields need copying for each type of object, as well as what custom objects should be created for an object. The later was accomplished with a Map of Schema.SObjectType to String i.e.:

Map<String, Schema.SObjectType> objectMap = new Map<String, Schema.SObjectType>{'Account' => new Custom_Object__c().getSObjectType()};

My method that creates objects when an account or contact is inserted takes a list of SObjects; how do we work out what object we need to create? Like this:

String objectInsertedType = insertedSObject.getSObjectType().getDescribe().getName();

Schema.SObjectType objectTypeToCreate = objectMap.get(objectInsertedType);

SObject newObject = objectTypeToCreate.newSObject();

In the first line, we get the name of the object being inserted (account or contact in our scenario) as a string, then use that string to get what type of record we should be creating from the Map we created earlier. Finally, we use the newSObject() method on the SObjectType object to give us an instance of the object we need to create. So far we've only done accounts – if an account is inserted, the newObject variable will be a Custom_Object__c object. But we can extend this for other objects by adding to the objectMap; for example if contacts should have records created of an object called Another_Custom_Object__c, we could put that into the map. Then, if a contact is inserted, newObject will be a Another_Custom_Object__c. And that way, we at least don't have duplicated logic that's essentially doing the same thing with a few small changes.

So that was how step 1 of making this functionality as generic as possible for use with different objects – implemented a way of determining what object should be created depending on what was inserted. As this has already turned into a fairly lengthy post, I’ll discuss how to copy values between the fields on the two objects in my next post. I’ll also discuss some of the pros and cons of this approach.

No comments:

Post a Comment