Merge pull request #44 from wimvelzeboer/feature/SObjects2_getGrandParentIdById

Add domain method to map the record Id to a grand-grand-...-parent record Id
This commit is contained in:
William Velzeboer
2022-05-25 13:55:37 +01:00
committed by GitHub
6 changed files with 238 additions and 1 deletions
@@ -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<Id, Id> getGrandParentIdById(List<fflib_ISObjects2> relatedDomains);
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>53.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -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<Id, Id> accountIdByInvoiceLineId =
* invoiceLineDomain.getGrandParentIdById(
* new List<fflib_SObjects2>
* {
* invoicesDomain,
* accountDomain
* });
*/
public virtual Map<Id, Id> getGrandParentIdById(List<fflib_ISObjects2> relatedDomains)
{
fflib_ISObjects2 relatedDomain = relatedDomains.remove(0);
SObjectType relatedDomainType = relatedDomain.getSObjectType();
Map<SObjectType, SObjectField> relatedFieldByDomainType = getRelatedFieldByDomainType();
if (relatedFieldByDomainType.containsKey(relatedDomainType) == false)
throw new DeveloperException(
String.format(
'RelatedDomain type {0} not present in method getRelatedFieldByDomainType',
new List<String> {String.valueOf(relatedDomainType)}));
Map<Id, Id> 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<Id, Id> 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<SObjectType, Schema.SObjectField> getRelatedFieldByDomainType()
{
return new Map<SObjectType, Schema.SObjectField>();
}
/**
* 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 { }
}
@@ -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<Id, Id> replaceValue(Map<Id, Id> source, Map<Id, Id> replacement)
{
Map<Id, Id> result = new Map<Id, Id>();
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
*
@@ -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<Account> {accountRecord});
DomainContacts contactDomain = new DomainContacts(new List<Contact> {contactRecordA, contactRecordB});
DomainCases caseDomain = new DomainCases(new List<Case> {caseRecordA, caseRecordB, caseRecordC});
// WHEN we request the relation between Case and Account
System.Test.startTest();
Map<Id, Id> result = caseDomain.getGrandParentIdById(
new List<fflib_SObjects2>
{
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<SObject> records)
{
super(records, Schema.Contact.SObjectType);
}
protected override Map<SObjectType, Schema.SObjectField> getRelatedFieldByDomainType()
{
return new Map<SObjectType, Schema.SObjectField>
{
Schema.Account.SObjectType => Contact.AccountId
};
}
}
private class DomainCases extends fflib_SObjects2
{
public DomainCases(List<SObject> records)
{
super(records, Schema.Case.SObjectType);
}
protected override Map<SObjectType, Schema.SObjectField> getRelatedFieldByDomainType()
{
return new Map<SObjectType, Schema.SObjectField>
{
Schema.Contact.SObjectType => Case.ContactId
};
}
}
}
@@ -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<Id, Id> source = new Map<Id, Id>
{
accountIdA => contactIdA,
accountIdB => contactIdB,
accountIdC => fflib_IDGenerator.generate(Schema.Contact.SObjectType)
};
Map<Id, Id> replacement = new Map<Id, Id>
{
contactIdA => accountIdB,
contactIdB => accountIdA
};
Map<Id, Id> 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()
{