Add comparing to related (parent) record field values

This commit is contained in:
Wim Velzeboer
2021-08-10 13:28:21 +01:00
parent 0277af7df2
commit 3540e56fe8
5 changed files with 373 additions and 9 deletions
+198 -2
View File
@@ -20,20 +20,32 @@ Adds a sub criteria with OR comparator
Adds a sub criteria with AND comparator
- <<equalTo, equalTo(Schema.SObjectField field, Object value)>> +
Add an equal to criteria comparing a fields value to a given value
- <<equalTo2, equalTo(String relatedField, Object value)>> +
Add an equal to criteria comparing a related fields value to a given value
- <<evaluate, evaluate(Object record)>> +
Evaluates the stored criteria based on the given SObject
- <<greaterOrEqualTo, greaterOrEqualTo(Schema.SObjectField field, Object value)>> +
Compares the fields values to be greater or equal to the given value
- <<greaterOrEqualTo2, greaterOrEqualTo(String relatedField, Object value)>> +
Compares the related field values to be greater or equal to the given value
- <<greaterThan, greaterThan(Schema.SObjectField field, Object value)>> +
Compares the fields values to be greater or equal to the given value
- <<greaterThan2, greaterThan(String relatedField, Object value)>> +
Compares the related field values to be greater or equal to the given value
- <<inSet, inSet(Schema.SObjectField field, Set<Object> values)>> +
Checks if the given sets contains the fields values
- <<lessOrEqualTo, lessOrEqualTo(Schema.SObjectField field, Object value)>> +
Compares the fields values to be less or equal to the given value
- <<lessOrEqualTo2, lessOrEqualTo(String relatedField, Object value)>> +
Compares the related field values to be less or equal to the given value
- <<lessThan, lessThan(Schema.SObjectField field, Object value)>> +
Compares the fields values to be greater or equal to the given value
- <<lessThan2, lessThan(String relatedField, Object value)>> +
Compares the related field values to be greater or equal to the given value
- <<notEqualTo, notEqualTo(Schema.SObjectField field, Object value)>> +
Add an equal to criteria comparing a fields value to a given value
- <<notEqualTo2, notEqualTo(String relatedField, Object value)>> +
Add an equal to criteria comparing a related fields value to a given value
- <<notInSet, notInSet(Schema.SObjectField field, Set<Date> values)>> +
Checks if the given sets does not contain the fields values
- <<toSQL, toSOQL()>> +
@@ -67,8 +79,6 @@ new fflib_Criteria()
___
[[andCriteria]]
=== andCriteria()
Changes the default comparator for each criteria to AND. +
@@ -181,6 +191,36 @@ new fflib_Criteria()
// Name = 'Example'
```
___
[[equalTo2]]
=== equalTo(String relatedField, Object value)
Add an equal to criteria comparing a related fields value to a given value
==== _Signature_
```java
public virtual fflib_Criteria equalTo(String relatedField, Object value)
```
==== _Parameters_
_**relatedField**_ +
The related field to evaluate
_**value**_ +
The value to be compared to the fields value
==== _Returns_
An instance of itself to enable method chaining
==== _Example_
```java
Contact record = [SELECT Id, Account.Name FROM Contact LIMIT 1];
new fflib_Criteria()
.equalTo('Account.Name', 'Example')
.evaluate(record)
// Evaluates:
// Account.Name = 'Example'
```
___
[[evaluate]]
=== evaluate(Object record)
Evaluates the stored criteria based on the given SObject
@@ -237,6 +277,38 @@ new fflib_Criteria()
// AnnualRevenue >= 54321
```
___
[[greaterOrEqualTo2]]
=== greaterOrEqualTo(String relatedField, Object value)
Compares the fields values to be greater or equal to the given value.
Evaluates to false if the field value is null.
==== _Signature_
```java
public virtual fflib_Criteria greaterOrEqualTo(String relatedField, Object value)
```
==== _Parameters_
_**relatedField**_ +
The relatedField name to use its value
_**value**_ +
The value to be compared to the fields value
==== _Returns_
An instance of itself to enable method chaining
==== _Example_
```java
Contact record = [SELECT Id, Account.AnnualRevenue FROM Contact LIMIT 1];
new fflib_Criteria()
.greaterOrEqualTo('Account.AnnualRevenue', 54321)
.evaluate(record);
// Evaluates:
// Account.AnnualRevenue >= 54321
```
___
[[greaterThan]]
=== greaterThan(Schema.SObjectField field, Object value)
@@ -267,6 +339,38 @@ new fflib_Criteria()
// AnnualRevenue > 54321
```
___
[[greaterThan2]]
=== greaterThan(String relatedField, Object value)
Compares the fields values to be greater or equal to the given value
Evaluates to false if the field value is null
==== _Signature_
```java
public virtual fflib_Criteria greaterThan(String relatedField, Object value)
```
==== _Parameters_
_**relatedField**_ +
The relatedField name to use its value
_**value**_ +
The value to be compared to the fields value
==== _Returns_
An instance of itself to enable method chaining
==== _Example_
```java
Contact record = [SELECT Id, Account.AnnualRevenue FROM Contact LIMIT 1];
new fflib_Criteria()
.greaterThan('Account.AnnualRevenue', 54321)
.evaluate(record);
// Evaluates:
// Account.AnnualRevenue > 54321
```
___
[[inSet]]
@@ -327,6 +431,37 @@ new fflib_Criteria()
// AnnualRevenue > 54321
```
___
[[lessOrEqualTo2]]
=== lessOrEqualTo(String relatedField, Object value)
Compares the related fields values to be less or equal to the given value
Evaluates to true if the field value is null
==== _Signature_
```java
public virtual fflib_Criteria lessOrEqualTo(String relatedField, Object value)
```
==== _Parameters_
_**relatedField**_ +
The relatedField name to use its value
_**value**_ +
The value to be compared to the fields value
==== _Returns_
An instance of itself to enable method chaining
==== _Example_
```java
Contact record = [SELECT Id, Account.AnnualRevenue FROM Contact LIMIT 1];
new fflib_Criteria()
.lessOrEqualTo('Account.AnnualRevenue', 54321)
.evaluate(record);
// Evaluates:
// Account.AnnualRevenue > 54321
```
___
[[lessThan]]
@@ -358,6 +493,37 @@ new fflib_Criteria()
// AnnualRevenue < 54321
```
[[lessThan2]]
=== lessThan(String relatedField, Object value)
Compares the related fields values to be less or equal to the given value
Evaluates to true if the field value is null
==== _Signature_
```java
public virtual fflib_Criteria lessThan(String relatedField, Object value)
```
==== _Parameters_
_**relatedField**_ +
The relatedField name to use its value
_**value**_ +
The value to be compared to the fields value
==== _Returns_
An instance of itself to enable method chaining
==== _Example_
```java
Contact record = [SELECT Id, Account.AnnualRevenue FROM Contact LIMIT 1];
new fflib_Criteria()
.lessThan('Account.AnnualRevenue', 54321)
.evaluate(record);
// Evaluates:
// AnnualRevenue < 54321
```
___
[[notEqualTo]]
@@ -388,6 +554,36 @@ new fflib_Criteria()
// Name != 'Example'
```
___
[[notEqualTo2]]
=== notEqualTo(String relatedField, Object value)
Add a not equal to criteria comparing a related fields value to a given value
==== _Signature_
```java
public virtual fflib_Criteria notEqualTo(String relatedField, Object value)
```
==== _Parameters_
_**relatedField**_ +
The relatedField name to evaluate
_**value**_ +
The value to be compared to the fields value
==== _Returns_
An instance of itself to enable method chaining
==== _Example_
```java
Contact record = [SELECT Id, Account.Name FROM Contact LIMIT 1];
new fflib_Criteria()
.notEqualTo('Account.Name', 'Example')
.evaluate(record);
// Evaluates:
// Account.Name != 'Example'
```
___
[[notInSet]]
=== notInSet(Schema.SObjectField field, Set<Object> values)
@@ -166,6 +166,12 @@ public virtual with sharing class fflib_Criteria
return this;
}
public virtual fflib_Criteria equalTo(String relatedField, Object value)
{
evaluators.add(new RelatedFieldEvaluator(relatedField, fflib_Operator.EQUALS, value));
return this;
}
/**
* Add a not equal to criteria while comparing a fields value to a given value
*
@@ -187,6 +193,12 @@ public virtual with sharing class fflib_Criteria
return this;
}
public virtual fflib_Criteria notEqualTo(String relatedField, Object value)
{
evaluators.add(new RelatedFieldEvaluator(relatedField, fflib_Operator.NOT_EQUALS, value));
return this;
}
/**
* Evaluates the stored criteria based on the given SObject
*
@@ -236,6 +248,12 @@ public virtual with sharing class fflib_Criteria
return this;
}
public virtual fflib_Criteria greaterOrEqualTo(String relatedField, Object value)
{
evaluators.add(new RelatedFieldEvaluator(relatedField, fflib_Operator.GREATER_THAN_OR_EQUAL_TO, value));
return this;
}
/**
* Compares the fields values to be greater or equal to the given value
* Evaluates to false if the field value is null
@@ -257,6 +275,11 @@ public virtual with sharing class fflib_Criteria
evaluators.add(new FieldEvaluator(field, fflib_Operator.GREATER_THAN, value));
return this;
}
public virtual fflib_Criteria greaterThan(String relatedField, Object value)
{
evaluators.add(new RelatedFieldEvaluator(relatedField, fflib_Operator.GREATER_THAN, value));
return this;
}
/**
* Compares the fields values to be less or equal to the given value
@@ -279,6 +302,11 @@ public virtual with sharing class fflib_Criteria
evaluators.add(new FieldEvaluator(field, fflib_Operator.LESS_THAN_OR_EQUAL_TO, value));
return this;
}
public virtual fflib_Criteria lessOrEqualTo(String relatedField, Object value)
{
evaluators.add(new RelatedFieldEvaluator(relatedField, fflib_Operator.LESS_THAN_OR_EQUAL_TO, value));
return this;
}
/**
@@ -302,6 +330,11 @@ public virtual with sharing class fflib_Criteria
evaluators.add(new FieldEvaluator(field, fflib_Operator.LESS_THAN, value));
return this;
}
public virtual fflib_Criteria lessThan(String relatedField, Object value)
{
evaluators.add(new RelatedFieldEvaluator(relatedField, fflib_Operator.LESS_THAN, value));
return this;
}
/**
* Checks if the given sets contains the fields values
@@ -568,10 +601,16 @@ public virtual with sharing class fflib_Criteria
return result + ')';
}
private interface Evaluator
{
Boolean evaluate(Object obj);
String toSOQL();
}
/**
* Generic criteria handler for comparing against sets
*/
private class FieldSetEvaluator implements Evaluator
private virtual class FieldSetEvaluator implements Evaluator
{
private Schema.SObjectField sObjectField;
private fflib_Objects values;
@@ -608,12 +647,6 @@ public virtual with sharing class fflib_Criteria
}
}
private interface Evaluator
{
Boolean evaluate(Object obj);
String toSOQL();
}
/**
* Generic field Evaluator
*/
@@ -654,6 +687,99 @@ public virtual with sharing class fflib_Criteria
}
}
private abstract class AbstractRelatedFieldEvaluator implements Evaluator
{
protected String fieldName;
protected fflib_Operator operator;
protected Object getFieldValue(Object record)
{
if (!fieldName.contains('.'))
{
return ((SObject) record).get(fieldName);
}
List<String> fieldPath = fieldName.split('\\.');
SObject relatedObject = (SObject) record;
for (Integer i = 0; i < fieldPath.size() - 1; i++)
{
relatedObject = relatedObject.getSObject(fieldPath.get(i));
}
return relatedObject.get(fieldPath.get(fieldPath.size()-1));
}
}
private class RelatedFieldEvaluator extends AbstractRelatedFieldEvaluator
{
private Object value;
public RelatedFieldEvaluator(String fieldName, fflib_Operator operator, Object value)
{
this.fieldName = fieldName;
this.value = value;
this.operator = operator;
}
public Boolean evaluate(Object record)
{
if (!(record instanceof SObject))
throw new CriteriaException('Only records of type SObject can have fieldCriteria');
Object fieldValue = getFieldValue(record);
return fflib_Comparator.compareTo(fieldValue, operator, this.value);
}
public String toSOQL()
{
return String.join(
new List<String>
{
this.fieldName,
operatorToString(this.operator),
toLiteral(this.value)
},
''
);
}
}
private class RelatedFieldSetEvaluator extends AbstractRelatedFieldEvaluator
{
private fflib_Objects values;
public RelatedFieldSetEvaluator(String fieldName, fflib_Operator operator, fflib_Objects values)
{
this.fieldName = fieldName;
this.values = values;
this.operator = operator;
}
public Boolean evaluate(Object record)
{
if (!(record instanceof SObject))
throw new CriteriaException('Only records of type SObject can have fieldCriteria');
Object fieldValue = getFieldValue(record);
Boolean isIn = this.values.contains(fieldValue);
return ((operator == fflib_Operator.INx && isIn) || (operator == fflib_Operator.NOT_IN && !isIn));
}
public String toSOQL()
{
return String.format(
'{0} {2} {3}',
new List<String>
{
this.fieldName,
operatorToString(this.operator),
toLiteral(this.values)
}
);
}
}
private class PropertyEvaluator implements Evaluator
{
private Object property;
@@ -0,0 +1,18 @@
/**
* File Name: fflib_MockSObjectUtil
*
* @author: architect ir. Wilhelmus G.J. Velzeboer
*/
@IsTest
public with sharing class fflib_MockSObjectUtil
{
public static SObject addRelatedParentObject(SObject childObject, SObject parentObject, String relationshipField)
{
String original = JSON.serialize(childObject);
return (SObject) JSON.deserialize(
original.left(original.length() - 1) +
',"' + relationshipField + '":' +
JSON.serialize(parentObject) + '}',
SObject.class);
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -550,4 +550,23 @@ private with sharing class fflib_CriteriaTest
.toSOQL()
);
}
@IsTest
static void itShouldEvaluateValuesOnRelatedObject()
{
// GIVEN a Contact record with related Account records
Contact contact = new Contact(LastName = 'Smith');
Account account = new Account(
Id = fflib_IDGenerator.generate(Account.SObjectType),
Name = 'Dummy'
);
SObject record = fflib_MockSObjectUtil.addRelatedParentObject(contact, account, 'Account');
// WHEN we run a criteria con the contact record validating related data on the parent Account
System.assert(
new fflib_Criteria()
.equalTo('Account.Name', 'Dummy')
.evaluate(record)
);
}
}