From 0b4d878382605e65966fde8bbeed61abec0bf7be Mon Sep 17 00:00:00 2001 From: WimVelzeboer Date: Wed, 25 May 2022 11:43:51 +0100 Subject: [PATCH] Add domain method to map the record Id to a grand-grand-...-parent record Id --- .../classes/domains/fflib_ISObjects2.cls | 33 ++++++++ .../domains/fflib_ISObjects2.cls-meta.xml | 5 ++ .../classes/domains/fflib_SObjects2.cls | 66 +++++++++++++++- .../classes/utils/fflib_ArrayUtils.cls | 30 ++++++++ .../classes/domains/fflib_SObjects2Test.cls | 77 +++++++++++++++++++ .../classes/utils/fflib_ArrayUtilsTest.cls | 28 +++++++ 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls create mode 100644 sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls-meta.xml diff --git a/sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls b/sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls new file mode 100644 index 0000000..5fa6102 --- /dev/null +++ b/sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls @@ -0,0 +1,33 @@ +/** + * File Name: fflib_ISObjects2 + * + * @author architect ir. Wilhelmus G.J. Velzeboer + * + * Copyright (c), W.G.J. Velzeboer, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above author notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the author nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +public interface fflib_ISObjects2 extends fflib_ISObjects +{ + Map getGrandParentIdById(List relatedDomains); +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls-meta.xml b/sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/default/classes/domains/fflib_ISObjects2.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/default/classes/domains/fflib_SObjects2.cls b/sfdx-source/apex-extensions/main/default/classes/domains/fflib_SObjects2.cls index 0da316f..71f9ba0 100644 --- a/sfdx-source/apex-extensions/main/default/classes/domains/fflib_SObjects2.cls +++ b/sfdx-source/apex-extensions/main/default/classes/domains/fflib_SObjects2.cls @@ -30,7 +30,7 @@ */ public virtual class fflib_SObjects2 extends fflib_SObjects - implements fflib_ISObjects + implements fflib_ISObjects2 { protected Schema.DescribeSObjectResult SObjectDescribe { get; private set; } @@ -79,6 +79,58 @@ public virtual class fflib_SObjects2 return (result.size() == 0) ? null : result.get(0); } + /** + * Creates a map between the record Ids of this domain to the last given grandparent domain. + * This method is using getRelatedFieldByDomainType to determine which SObjectField + * is related to the given parent domain + * + * @param relatedDomains A List of domains in hierarchical order from first parent to grandparent + * + * @return Returns a map with KEY: The Ids of the records in this domain, + * to the VALUE: The Id of the last grandparent record listed in the relatedDomains, + * null if no grandparent exists. + * + * @see getRelatedFieldByDomainType + * + * @example + * IInvoiceLines invoiceLineDomain = InvoiceLines.newInstance(invoiceLineRecords); + * IInvoices invoicesDomain = Invoices.newInstance(invoiceRecords); + * IAccount accountDomain = Accounts.newInstance(accountRecords); + * + * // A map with key - InvoiceLine.Id to value - Account.Id + * Map accountIdByInvoiceLineId = + * invoiceLineDomain.getGrandParentIdById( + * new List + * { + * invoicesDomain, + * accountDomain + * }); + */ + public virtual Map getGrandParentIdById(List relatedDomains) + { + fflib_ISObjects2 relatedDomain = relatedDomains.remove(0); + SObjectType relatedDomainType = relatedDomain.getSObjectType(); + Map relatedFieldByDomainType = getRelatedFieldByDomainType(); + + if (relatedFieldByDomainType.containsKey(relatedDomainType) == false) + throw new DeveloperException( + String.format( + 'RelatedDomain type {0} not present in method getRelatedFieldByDomainType', + new List {String.valueOf(relatedDomainType)})); + + Map result = getIdFieldByIdField( + relatedFieldByDomainType.get(relatedDomainType), + getSObjectType().getDescribe().fields.getMap().get('Id')); + + // Check if we need to resolve another parent relation, if not return the results + if (relatedDomains.isEmpty()) return result; + + Map grandChildIdByChildId = relatedDomain.getGrandParentIdById(relatedDomains); + + // Replace the maps value (childId) with the grand child Id + return fflib_ArrayUtils.replaceValue(result, grandChildIdByChildId); + } + /** * @return Returns the contents of the Domain by their primary Key ('Id' field) */ @@ -597,6 +649,16 @@ public virtual class fflib_SObjects2 return result; } + /** + * Get Parent Domain SObjectType to SObjectField.relationship field on this domain SObjectType + * + * @return Returns a map of the Parent Domain SObjectType to SObjectField.relationship field on the SObjectType + */ + protected virtual Map getRelatedFieldByDomainType() + { + return new Map(); + } + /** * Get a map with the record mapped to the given Id field value * Key fields containing null values are omitted @@ -892,4 +954,6 @@ public virtual class fflib_SObjects2 } } + + public class DeveloperException extends Exception { } } \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/default/classes/utils/fflib_ArrayUtils.cls b/sfdx-source/apex-extensions/main/default/classes/utils/fflib_ArrayUtils.cls index ea2c8c2..2932c19 100644 --- a/sfdx-source/apex-extensions/main/default/classes/utils/fflib_ArrayUtils.cls +++ b/sfdx-source/apex-extensions/main/default/classes/utils/fflib_ArrayUtils.cls @@ -310,6 +310,36 @@ public class fflib_ArrayUtils return results; } + /** + * Takes two maps and replaces the value of the source with the value of the replacement, + * where the source value matches the replacement key. + * The value will become null if the source value does not exists as key in replacement map + * + * @param source The map to replace its value + * @param replacement The map to use its value for replacement + * + * @return Returns a Map<{Source} Id, {Replacement} Id> + */ + public static Map replaceValue(Map source, Map replacement) + { + Map result = new Map(); + for (Id keyId : source.keySet()) + { + Id sourceValue = source.get(keyId); + + if (replacement.containsKey(sourceValue)) + { + result.put(keyId, replacement.get(sourceValue)); + } + else + { + // There is no replacement value for this keyId + result.put(keyId, null); + } + } + return result; + } + /** * Reverse the order of a list of objects * diff --git a/sfdx-source/apex-extensions/tests/classes/domains/fflib_SObjects2Test.cls b/sfdx-source/apex-extensions/tests/classes/domains/fflib_SObjects2Test.cls index f0f3497..11db36b 100644 --- a/sfdx-source/apex-extensions/tests/classes/domains/fflib_SObjects2Test.cls +++ b/sfdx-source/apex-extensions/tests/classes/domains/fflib_SObjects2Test.cls @@ -120,6 +120,51 @@ private class fflib_SObjects2Test // todo - add assertions } + @IsTest + static void itShouldReturnTheRelatedParentDomainId() + { + // GIVEN a case related to contact related to Account , + // a case record related to contact but not to Account, + // and a case record related to nothing. + // All encapsulated in domains. + Id caseIdA = fflib_IDGenerator.generate(Schema.Case.SObjectType); + Id caseIdB = fflib_IDGenerator.generate(Schema.Case.SObjectType); + Id caseIdC = fflib_IDGenerator.generate(Schema.Case.SObjectType); + Id contactIdA = fflib_IDGenerator.generate(Schema.Contact.SObjectType); + Id contactIdB = fflib_IDGenerator.generate(Schema.Contact.SObjectType); + Id accountId = fflib_IDGenerator.generate(Schema.Account.SObjectType); + Account accountRecord = new Account(Id = accountId); + Contact contactRecordA = new Contact(Id = contactIdA, AccountId = accountId); + Contact contactRecordB = new Contact(Id = contactIdB); + Case caseRecordA = new Case(Id = caseIdA, ContactId = contactIdA); + Case caseRecordB = new Case(Id = caseIdB, ContactId = contactIdB); + Case caseRecordC = new Case(Id = caseIdC); + + DomainAccounts accountDomain = new DomainAccounts(new List {accountRecord}); + DomainContacts contactDomain = new DomainContacts(new List {contactRecordA, contactRecordB}); + DomainCases caseDomain = new DomainCases(new List {caseRecordA, caseRecordB, caseRecordC}); + + // WHEN we request the relation between Case and Account + System.Test.startTest(); + Map result = caseDomain.getGrandParentIdById( + new List + { + contactDomain, + accountDomain + } + ); + System.Test.stopTest(); + + // THEN it should return the AccountId By CaseId + System.assertEquals(3, result.size(), 'Expected one related account to the case'); + System.assert(result.containsKey(caseIdA)); + System.assert(result.containsKey(caseIdB)); + System.assert(result.containsKey(caseIdC)); + System.assertEquals(accountId, result.get(caseIdA), 'Account Id should be related to the Case Id A'); + System.assertEquals(null, result.get(caseIdB), 'Case Id B should have no related Account Id'); + System.assertEquals(null, result.get(caseIdC), 'Case Id B should have no related Account Id'); + } + @IsTest static void itShouldReturnStringById() { @@ -614,4 +659,36 @@ private class fflib_SObjects2Test clearField(Schema.Account.ShippingCountry); } } + + private class DomainContacts extends fflib_SObjects2 + { + public DomainContacts(List records) + { + super(records, Schema.Contact.SObjectType); + } + + protected override Map getRelatedFieldByDomainType() + { + return new Map + { + Schema.Account.SObjectType => Contact.AccountId + }; + } + } + + private class DomainCases extends fflib_SObjects2 + { + public DomainCases(List records) + { + super(records, Schema.Case.SObjectType); + } + + protected override Map getRelatedFieldByDomainType() + { + return new Map + { + Schema.Contact.SObjectType => Case.ContactId + }; + } + } } diff --git a/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls b/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls index cce7ced..2efe0a5 100644 --- a/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls +++ b/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls @@ -238,6 +238,34 @@ private class fflib_ArrayUtilsTest System.assertEquals(8, result, 'Did not return the last value in the list of objects'); } + @IsTest + static void itShouldReplaceValueInTheMap() + { + Id accountIdA = fflib_IDGenerator.generate(Schema.Account.SObjectType); + Id accountIdB = fflib_IDGenerator.generate(Schema.Account.SObjectType); + Id accountIdC = fflib_IDGenerator.generate(Schema.Account.SObjectType); + Id contactIdA = fflib_IDGenerator.generate(Schema.Contact.SObjectType); + Id contactIdB = fflib_IDGenerator.generate(Schema.Contact.SObjectType); + Map source = new Map + { + accountIdA => contactIdA, + accountIdB => contactIdB, + accountIdC => fflib_IDGenerator.generate(Schema.Contact.SObjectType) + }; + Map replacement = new Map + { + contactIdA => accountIdB, + contactIdB => accountIdA + }; + + Map result = fflib_ArrayUtils.replaceValue(source, replacement); + + System.assertEquals(3, result.size()); + System.assertEquals(accountIdB, result.get(accountIdA)); + System.assertEquals(accountIdA, result.get(accountIdB)); + System.assertEquals(null, result.get(accountIdC)); + } + @IsTest static void itShouldReturnAllValuesForTheGivenIds() {