Here is my solution. It works dynamically on all fields of certain types (I have tested with String, Picklist, Date, DateTime, Integer, Boolean). You could extend it so that not only updates, but also inserts are tracked. Edit: For some weeks, this code has been working in production without problems.
Be aware that it can compromise security because changes to fields are tracked even if the user cannot see the field on the page layout.
The object for which the history information will be kept is called Compliance__c
; the history is stored in ComplianceHistory__c
.
This is inside the after update trigger for Compliance__c
:
//get all fields from compliance that we want to check for changes
Map<String, Schema.SObjectField> allComplFieldsMap = Schema.SObjectType.Compliance__c.fields.getMap();
complFieldsToTrack = new Map<String, Schema.DescribeFieldResult>();
for (Schema.SObjectField complField : allComplFieldsMap.values()) {
Schema.DescribeFieldResult describeResult = complField.getDescribe();
//choose which fields to track depending on the field type
if (describeResult.getType() == Schema.DisplayType.Boolean ||
describeResult.getType() == Schema.DisplayType.Combobox ||
describeResult.getType() == Schema.DisplayType.Currency ||
describeResult.getType() == Schema.DisplayType.Date ||
describeResult.getType() == Schema.DisplayType.DateTime ||
describeResult.getType() == Schema.DisplayType.Double ||
describeResult.getType() == Schema.DisplayType.Email ||
describeResult.getType() == Schema.DisplayType.Integer ||
describeResult.getType() == Schema.DisplayType.MultiPicklist ||
describeResult.getType() == Schema.DisplayType.Percent ||
describeResult.getType() == Schema.DisplayType.Phone ||
describeResult.getType() == Schema.DisplayType.Picklist ||
describeResult.getType() == Schema.DisplayType.String ||
describeResult.getType() == Schema.DisplayType.TextArea ||
describeResult.getType() == Schema.DisplayType.Time ||
describeResult.getType() == Schema.DisplayType.URL)
{
//don't add standard fields that are not necessary
if (describeResult.getName() != 'CreatedDate' &&
describeResult.getName() != 'LastModifiedDate' &&
describeResult.getName() != 'SystemModstamp' &&
//only add fields that are visible to the current user
describeResult.isAccessible() &&
//do not add formula fields
!describeResult.isCalculated()
)
{
complFieldsToTrack.put(describeResult.getName(), describeResult);
}
}
}
The following is done for every object in Trigger.new inside a loop:
Compliance__c oldCompl = (Compliance__c)oldSo;
Compliance__c newCompl = (Compliance__c)so;
for (Schema.DescribeFieldResult fieldDescribe : complFieldsToTrack.values()) {
if (oldCompl.get(fieldDescribe.getName()) != newCompl.get(fieldDescribe.getName())) {
ComplianceHistory__c complHistory = createUpdateHistory(fieldDescribe, oldCompl, newCompl);
historiesToInsert.add(complHistory);
}
}
Here is the method to populate the history object referenced in the loop:
private ComplianceHistory__c createUpdateHistory(Schema.DescribeFieldResult field, Compliance__c oldCompl, Compliance__c newCompl) {
ComplianceHistory__c complHistory = new ComplianceHistory__c();
complHistory.Compliance__c = newCompl.Id;
complHistory.Event__c = 'Edit';
complHistory.Field__c = field.getLabel();
complHistory.User__c = UserInfo.getUserId();
// shorten strings that are longer than 255 characters (can happen if the field has the type textArea)
if (complHistory.OldValue__c != null) complHistory.OldValue__c = complHistory.OldValue__c.abbreviate(255);
if (complHistory.NewValue__c != null) complHistory.NewValue__c = complHistory.NewValue__c.abbreviate(255);
complHistory.EditDate__c = System.now();
return complHistory;
}
In the end, the history records are inserted:
if (!historiesToInsert.isEmpty()) {
//remove duplicate history entries
List<ComplianceHistory__c> historiesToInsertWithoutDuplicates = new List<ComplianceHistory__c>();
Set<ComplianceHistory__c> historiesSet = new Set<ComplianceHistory__c>();
historiesSet.addAll(historiesToInsert);
historiesToInsertWithoutDuplicates.addAll(historiesSet);
//insert the rest
insert historiesToInsertWithoutDuplicates;
}
There is no way to get Id for a custom or standard field except it you can retrieve name, label type, attributes etc..
The only help can be made is custom field id start from prefix 00N
and in production CF
appended to 00N
In addition there is a idea for which you can vote.
Conclusion: getDescribe()
don't support id for field.
Best Answer
When you collect the fields; you have a check
isUpdateable
; where you filter out the fields which are allowed to be updated by user.As all fields on
*History
object (AssetHistory
here) are read only, you do not see any fields in result.Not sure what you are trying to do, removing
isUpdateable
condition will show you all the fields.