0

Our org uses a mix of a variety of open source packages including bluewolf-beyond/selector

This is a nifty package that allows construction of sobject list filters using a builder pattern that is more readable and reusable than classic Apex.

For example (here, in an fflib domain method for Assets.cls):

private List<SObject> getCancelledAssets(Map<Id,Sobject> existingRecords) {

   Select.Filter hasCancelledStatusFilter =
    Select.Field.isEqual(Asset.Status, 'Cancelled');

   if (existingRecords != null) {
    return Select.Field.hasChanged(Asset.Status)
       .andX(hasCancelledStatusFilter)
       .filter(Records,existingRecords);
   }
   else {
     return hasCancelledStatusFilter.filter(Records);
   }
}

When running this, the developer got this stacktrace :

System.NullPointerException
LineNumber:39
Message:Attempt to de-reference a null object
Stacktrace:Class.Select.FieldReference.SchemaFieldReference.getFrom: line 39, column 1
Class.Select.FieldChangedPredicate.evaluate: line 22, column 1
Class.Select.AndPredicate.evaluate: line 51, column 1
...

What could be the reason?

1

Well, in retrospect, the answer is pretty obvious but as the bluewolf-beyond/selector package was fairly new to the developer in question, the developer got frustrated and wanted to revert to classic Apex...

Basically, the clue in the stacktrace was:

Class.Select.FieldChangedPredicate.evaluate: line 22, column 1

this meant the filter Select.Field.hasChanged(Asset.Status) was being evaluated. And this meant that existingRecords was not null. This in turn implied that we were in a before/after update trigger.

But the test case was after insert - so existingRecords should have been null.

Tracing higher up in the code, the domain method was calling

getCancelledAssets(Map<Id,Sobject> existingRecords)

with an empty map in the onAfterInsert handler so the if (existingRecords != null) was TRUE in the after insert use case!

The fix (as we prefer empty collections over nulls) was simply:

private List<SObject> getCancelledAssets(Map<Id,Sobject> existingRecords) {

   Select.Filter hasCancelledStatusFilter =
    Select.Field.isEqual(Asset.Status, 'Cancelled');

   if (!existingRecords.isEmpty()) {
     return Select.Field.hasChanged(Asset.Status)
        .andX(hasCancelledStatusFilter)
        .filter(Records,existingRecords);
   }
   else {
     return hasCancelledStatusFilter.filter(Records);
   }
}   

Bottom line - don't be afraid to poke into the open source code to understand what it is doing to gain insights into what you are doing wrong.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.