Is this possible?
As Vamsi Krishna mentioned in a comment on your question, this is possible.
The two methods he mentioned are:
- Enable field history tracking for the field (or fields) that you wish to track
- Create a new custom (datetime) field, and create a workflow rule that updates this new datetime field every time your target field is modified
Method 1 is the easiest to set up, and scales up to 20 fields (this is a limit imposed by Salesforce). It takes a little more work to pull the information for use in an Apex class, and has another important limitation that I'll get to later.
Method 2 is the easiest to use in an Apex class, but takes longer to set up and doesn't scale well at all (you need a new custom field, a new workflow rule, and a new field update for each field you want to track in this manner). I wouldn't recommend this to anyone unless you only need to track one field. Even then, this method incurs more technical debt than I'm comfortable with.
How to use tracked field history
Making use of field history tracking information in an Apex class requires a query.
For standard objects (Account
, Case
, Contact
, etc...), the object you'll be querying is simply <sObjectName>History
(AccountHistory
, CaseHistory
, ContactHistory
, etc...)
For custom objects, you'll be querying the respective __History
object (the history object for My_Custom_Object__c
is My_Custom_Object__History
). You just replace __c
with __History
The history tracking object always comes with the same 8 fields, CreatedById
, CreatedDate
, Field
, Id
, isDeleted
, NewValue
, OldValue
. The 8th field is a lookup to the object that history is being tacked on. For Custom Objects, this appears to always be ParentId
. For Standard Objects, this is <sObjectName>Id
(AccountId
, CaseId
, ContactId
, etc...)
Assuming you're working in a Trigger, or otherwise have access to Trigger Context Variables, your query would end up looking like this
[SELECT ParentId, Field, CreatedById FROM My_Custom_Object__History WHERE ParentId IN :trigger.newMap.keySet()]
Now, the problem with making business logic depend on tracked field history is that it is currently impossible to insert data into the history object in a unit test. You can't insert directly into history objects, and field history is not tracked in unit tests (even if the field is set up for history tracking).
You could manage to test the logic that depends on field history by wrapping your history query inside an if(test.isrunningtest())
, and providing your own 'history' records in your test, but this limitation was a dealbreaker for me.
The other limitation is that you can't track field history on formula fields, autonumber fields, or rollup summary fields.
Another option
To get around the limitations of standard history tracking (if you need to track more than 20 fields, track a formula field, or want to reasonably test your code), you'd need to use a different method than the two listed above:
- Create your own Custom History object, as well as an interface to use it
This option is more work up front, but gives you the most freedom.
I've done this in my org so that the interface to my custom History__c
object is sObject agnostic, and only requires a single line of code to use in my Update triggers.
Adding or removing fields from my Custom History tracking is a matter of updating a fieldset (which can be done without a deployment).
Adding a new sObject to my Custom History tracking requires a new lookup field on my custom History__c
object as well as a new fieldset on the newly tracked object.