Salesforce developer
Musings of a Salesforce.com and Force.com developer.
Friday 26 August 2011
Protecting code from configuration
Record Types
Wednesday 9 February 2011
Describe, and Schema, and SObject and Oh my! Part 2
In my previous post, I talked about using Schema, SObject and Describe objects to help make code generic and usable across different types of objects. To recap: when creating/updating/deleting an object, a corresponding object should be created/updated/deleted with a small sub-set of details copied across to the new record. The previous post looked at how to automatically determine the type of object should be created (or edited or deleted – I'm sure you get the idea by now) when a record is inserted.
So we have a way of working out what type of object we need to create. How do we go about copying values between the different types of object? As different objects have different fields, we need a way of knowing which fields to copy for which objects. Once again we turn to our good friend Map:
Map<String, Map<String, String> > fieldsByObjectName =
new Map<String, Map<String, String>> {'Account' => new Map
What an eyesore – but not as difficult as it may first look. We have a map (let's call it the outer map) where the values themselves are a map (inner maps). The inner maps hold the details of what fields should be copied for an object; we can get these values from the outer map by get()ing the values associated with the object name of the object being inserted.
Let's look how that applies to the example above. fieldsByObjectName.get('Account') returns a map of strings, keyed by strings. These hold the fields that should be copied from the account to the custom object, and what the corresponding field is on the custom object.
Usefully, the generic SObject has put() and get() methods to retrieve and set field values. Get() takes a field on an SObject for which the value should be returned; put() takes a field and a value and sets the field to the value given. As we have these values in a collection, we can simply loop over the collection and copy the fields with these methods:
Map<String, String> fieldsToCopy = fieldsByObjectName.get(objectName);
for(String field : fieldsToCopy.keySet()) {
newObject.put(fieldsToCopy.get(field), ourNewObject.get(field));
}
Some relatively straightforward DML plumbing at the end of the code handles inserts/updates of the necessary objects.
So, the end result is code that will extend quite nicely if I need to do this for other objects. I just need to create a new custom object with fields to hold values; update the code to map the object to the new custom object, along with what fields should be copied; and finally add a simple trigger that calls the relevant method depending on the type of operation performed.
So are there any downsides?
Though the code is extendable, I don’t think it’s as easy to read as doing it the long-handed way. There’s a whole discussion to be had about the pros and cons of this, however, I think that it would take someone looking at the code longer to understand this approach than simply having triggers and code for each object.
My solution doesn’t do anything to convert between different field types – not really an issue for what I was doing, but if you had a source field of one type and a destination field of another type, some sort of conversion may need to take place. At this time, I'm not sure how I'd approach that.
Where updates occur to objects, I had to hard code queries to retrieve the objects with the sub-set of details so that I could update them (or delete them in the deletion case). This is because I had a set of IDs to use in the query, and had I constructed a query string, there was no easy way to include them in the string. This did feel as if I was breaking the principle that led me down the route in the first place. However, it was “just” a proof of concept and, more importantly, a learning opportunity for me and all things considered, I’m pretty happy with the end result.
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.:
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.
Wednesday 8 September 2010
Static boolean variables as "Locks"
So - do you use static boolean variables to control trigger flow and behaviour? I do - a lot. A common use is when Object A has a trigger that does something to Object B, and Object B has a trigger that does something to Object A. To avoid recursion when you update one, you can use a static boolean to cause the second trigger to return immediately rather than execute it, which causes the first trigger to fire again yadda yadda yadda. Similarly, if you have workflow that will cause an update to a record and you don't want triggers firing a second time, a static boolean does the job.
The advice in this post is simple: I think it's a good idea to keep these variables in a separate class. This is due to recent experiences, when I needed such a static variable in a class. I took the easy route and put it in the same class, and this was OK initially. Later, I needed to access that variable from somewhere else, which was possible, however, in that same class I also had a static code block in the same class that queried the database and set some variables.
Now, just by looking at this variable from outside the class, I was causing queries against the database to happen. I had enough headroom for this at the time but in the long term, it's a potential headache waiting to happen.
The solution is to have a separate class that holds such variables and refer to them from other points of code that need to look at them, rather than have the booleans amongst classes scattered here, there and every where. It'll be easier to manage and is better in the long run.
Friday 5 February 2010
Too many script statements?
I'll be posting about some of the lessons learned in upcoming posts, but I just wanted to take a moment to an idea I have up on Ideas. The governor limit on script statements has been my main obstacle this time around; I know that it's part of multi-tenancy and working with Salesforce. I don't actually mind operating within those limits, but I do like being able to handle meeting those limits in a sensible way.
The reason that "Too many script statements" is special is that, when hitting other governor limits, you can catch them as exceptions and handle them appropriately - usually displaying an error to the user. However, once you've used too many script statements, you don't have any script statements left to tidy up or present something meaningful to the user.
There are ways around this - not hitting the limit in the first place (not a practical solution this time round), periodically checking how many statements have been used and throwing an exception at a threshold (fiddly, unpredictable and uses script statements!). But a neater solution would be nice.
Feel my pain? Don't mope, promote!
Wednesday 25 November 2009
Lookup filter - a cautionary tale
I'm at the "fun" stage of the project where I'm writing my test methods and encountered an unusual problem. We have a custom object, I created some instances of this object and saved them to the database. This object also has an (optional) lookup field to another instance of the same object, so I then tried to update some of the inserted objects with references to other inserted objects.
When running the test code, I was getting an error message along the lines of:
"FIELD_INTEGRITY_EXCEPTION: Value does not exist or does not match filter criteria. Click icon to select a value."
Turns out the error was related to a feature in beta, "Filter on Lookup". To be fair, it wasn't the feature's fault: it was being used incorrectly which was causing the problem, and it wasn't an obvious culprit.
The Idea requests a way to restrict what records are shown when a look up button is pressed. For example, let's say you use the Account object for two types of account, customer and partner. On the Opportunity object, you may want fields related to the customer you are trying to sell to, and the partner you are partnering with on the deal. You want to stop users putting partners in the customer field and vice versa. Sharing rules are no good as you want users to be able to see both type of account. With filter on lookup, users click the lookup button on the customer field, and only see customer accounts, and partner accounts when clicking the partner lookup.
It also stops you manually entering names of accounts that do not meet the filter criteria. It's actually slightly cleverer than this, because whatever way you try to save a record, it will check the related object matches the filter; if it doesn't an error is reported...
Which is where the problem I encountered comes into play: the field I was trying to set in my test method had such a filter set on it, and the filter had been set up incorrectly - it was looking at the wrong field, which hadn't been set by my test code. The result: error! Hope someone finds this useful...