From 3540e56fe8705dbdfee18146f4c1d33f340171f3 Mon Sep 17 00:00:00 2001 From: Wim Velzeboer Date: Tue, 10 Aug 2021 13:28:21 +0100 Subject: [PATCH] Add comparing to related (parent) record field values --- docs/fflib_Criteria.asciidoc | 200 +++++++++++++++++- .../classes/criteria/fflib_Criteria.cls | 140 +++++++++++- .../classes/utils/fflib_MockSObjectUtil.cls | 18 ++ .../utils/fflib_MockSObjectUtil.cls-meta.xml | 5 + .../classes/criteria/fflib_CriteriaTest.cls | 19 ++ 5 files changed, 373 insertions(+), 9 deletions(-) create mode 100644 sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls create mode 100644 sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls-meta.xml diff --git a/docs/fflib_Criteria.asciidoc b/docs/fflib_Criteria.asciidoc index a920493..33e8ea2 100644 --- a/docs/fflib_Criteria.asciidoc +++ b/docs/fflib_Criteria.asciidoc @@ -20,20 +20,32 @@ Adds a sub criteria with OR comparator Adds a sub criteria with AND comparator - <> + Add an equal to criteria comparing a fields value to a given value +- <> + +Add an equal to criteria comparing a related fields value to a given value - <> + Evaluates the stored criteria based on the given SObject - <> + Compares the fields values to be greater or equal to the given value +- <> + +Compares the related field values to be greater or equal to the given value - <> + Compares the fields values to be greater or equal to the given value +- <> + +Compares the related field values to be greater or equal to the given value - < values)>> + Checks if the given sets contains the fields values - <> + Compares the fields values to be less or equal to the given value +- <> + +Compares the related field values to be less or equal to the given value - <> + Compares the fields values to be greater or equal to the given value +- <> + +Compares the related field values to be greater or equal to the given value - <> + Add an equal to criteria comparing a fields value to a given value +- <> + +Add an equal to criteria comparing a related fields value to a given value - < values)>> + Checks if the given sets does not contain the fields values - <> + @@ -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 values) diff --git a/sfdx-source/apex-extensions/main/default/classes/criteria/fflib_Criteria.cls b/sfdx-source/apex-extensions/main/default/classes/criteria/fflib_Criteria.cls index 4e6274a..c582421 100644 --- a/sfdx-source/apex-extensions/main/default/classes/criteria/fflib_Criteria.cls +++ b/sfdx-source/apex-extensions/main/default/classes/criteria/fflib_Criteria.cls @@ -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 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 + { + 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 + { + this.fieldName, + operatorToString(this.operator), + toLiteral(this.values) + } + ); + } + } + private class PropertyEvaluator implements Evaluator { private Object property; diff --git a/sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls b/sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls new file mode 100644 index 0000000..131524f --- /dev/null +++ b/sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls @@ -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); + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls-meta.xml b/sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls-meta.xml new file mode 100644 index 0000000..d75b058 --- /dev/null +++ b/sfdx-source/apex-extensions/main/default/classes/utils/fflib_MockSObjectUtil.cls-meta.xml @@ -0,0 +1,5 @@ + + + 51.0 + Active + diff --git a/sfdx-source/apex-extensions/tests/classes/criteria/fflib_CriteriaTest.cls b/sfdx-source/apex-extensions/tests/classes/criteria/fflib_CriteriaTest.cls index 1ca9cb7..1ae755d 100644 --- a/sfdx-source/apex-extensions/tests/classes/criteria/fflib_CriteriaTest.cls +++ b/sfdx-source/apex-extensions/tests/classes/criteria/fflib_CriteriaTest.cls @@ -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) + ); + } } \ No newline at end of file