Add getParentFieldById methods to fflib_SObjects2 (#73)
This method can fetch deeply related data which is distributed over multiple related domains
This commit is contained in:
@@ -55,7 +55,10 @@ Some of the changes that have major impact are listed here;
|
||||
_mergeMaps_,<br/>Add method overload to merge two maps of idByBoolean,
|
||||
- **fflib_SObjects2**<br/>
|
||||
The following methods have been added to the SObjects2 class:<br/>
|
||||
_getBooleanFieldByIdField_
|
||||
_getBooleanFieldByIdField_<br/>
|
||||
_getParentBooleanFieldById_<br/>
|
||||
_getParentDecimalFieldById_<br/>
|
||||
_getParentStringFieldById_<br/>
|
||||
- **API Upgrades**
|
||||
Upgraded to API 65.0
|
||||
|
||||
|
||||
@@ -104,6 +104,129 @@ public virtual class fflib_SObjects2
|
||||
return (result.size() == 0) ? null : result.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field value from the last given grandparent domain by the record Id
|
||||
*
|
||||
* @param valueField The field use return as value for the map
|
||||
* @param domainsByRelationshipField A Map of domains and the 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 value of the given valueField of the last grandparent record listed in the relatedDomains,
|
||||
* null if no grandparent exists.
|
||||
*
|
||||
* @example
|
||||
* ICases casesDomain = Cases.newInstance(caseRecords);
|
||||
* IContacts contactsDomain = Contacts.newInstance(contactRecords);
|
||||
* IAccount accountDomain = Accounts.newInstance(accountRecords);
|
||||
*
|
||||
* // A map with key - Case.Id to value - Account.Id
|
||||
* Map<Id, String> accountNameByCaseId =
|
||||
* casesDomain.getParentStringFieldById(
|
||||
* Schema.Account.Name,
|
||||
* new Map<Schema.SObjectField, fflib_SObjects2>
|
||||
* {
|
||||
* Schema.Case.ContactId => contactDomain,
|
||||
* Schema.Contact.AccountId => accountDomain
|
||||
* });
|
||||
*/
|
||||
public virtual Map<Id, String> getParentStringFieldById(
|
||||
Schema.SObjectField valueField,
|
||||
Map<Schema.SObjectField, fflib_SObjects2> domainsByRelationshipField)
|
||||
{
|
||||
Map<Id, Id> parentIdByChildId = getParentIdById(domainsByRelationshipField);
|
||||
|
||||
List<fflib_SObjects2> domains = domainsByRelationshipField.values();
|
||||
fflib_SObjects2 lastGrandParentDomain = domains.get(domains.size() - 1);
|
||||
|
||||
return fflib_ArrayUtils.replaceValue(
|
||||
parentIdByChildId,
|
||||
lastGrandParentDomain.getStringFieldByIdField(
|
||||
valueField,
|
||||
lastGrandParentDomain.getSObjectType().getDescribe().fields.getMap().get('Id')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field value from the last given grandparent domain by the record Id
|
||||
*
|
||||
* @param valueField The field use return as value for the map
|
||||
* @param domainsByRelationshipField A Map of domains and the 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 value of the given valueField of the last grandparent record listed in the relatedDomains,
|
||||
* null if no grandparent exists.
|
||||
*
|
||||
* @example
|
||||
* ICases casesDomain = Cases.newInstance(caseRecords);
|
||||
* IContacts contactsDomain = Contacts.newInstance(contactRecords);
|
||||
* IAccount accountDomain = Accounts.newInstance(accountRecords);
|
||||
*
|
||||
* // A map with key - Case.Id to value - Account.Id
|
||||
* Map<Id, Object> accountNameByCaseId =
|
||||
* casesDomain.getParentDecimalFieldById(
|
||||
* Schema.Account.Name,
|
||||
* new Map<Schema.SObjectField, fflib_SObjects2>
|
||||
* {
|
||||
* Schema.Case.ContactId => contactDomain,
|
||||
* Schema.Contact.AccountId => accountDomain
|
||||
* });
|
||||
*/
|
||||
public virtual Map<Id, Decimal> getParentDecimalFieldById(
|
||||
Schema.SObjectField valueField,
|
||||
Map<Schema.SObjectField, fflib_SObjects2> domainsByRelationshipField)
|
||||
{
|
||||
Map<Id, Id> parentIdByChildId = getParentIdById(domainsByRelationshipField);
|
||||
|
||||
List<fflib_SObjects2> domains = domainsByRelationshipField.values();
|
||||
fflib_SObjects2 lastGrandParentDomain = domains.get(domains.size() - 1);
|
||||
|
||||
return fflib_ArrayUtils.replaceValue(
|
||||
parentIdByChildId,
|
||||
lastGrandParentDomain.getDecimalById(
|
||||
valueField,
|
||||
lastGrandParentDomain.getSObjectType().getDescribe().fields.getMap().get('Id')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field value from the last given grandparent domain by the record Id
|
||||
*
|
||||
* @param valueField The field use return as value for the map
|
||||
* @param domainsByRelationshipField A Map of domains and the 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 value of the given valueField of the last grandparent record listed in the relatedDomains,
|
||||
* null if no grandparent exists.
|
||||
*
|
||||
* @example
|
||||
* ICases casesDomain = Cases.newInstance(caseRecords);
|
||||
* IContacts contactsDomain = Contacts.newInstance(contactRecords);
|
||||
* IAccount accountDomain = Accounts.newInstance(accountRecords);
|
||||
*
|
||||
* // A map with key - Case.Id to value - Account.Id
|
||||
* Map<Id, Object> accountNameByCaseId =
|
||||
* casesDomain.getParentBooleanFieldById(
|
||||
* Schema.Account.Name,
|
||||
* new Map<Schema.SObjectField, fflib_SObjects2>
|
||||
* {
|
||||
* Schema.Case.ContactId => contactDomain,
|
||||
* Schema.Contact.AccountId => accountDomain
|
||||
* });
|
||||
*/
|
||||
public virtual Map<Id, Boolean> getParentBooleanFieldById(
|
||||
Schema.SObjectField valueField,
|
||||
Map<Schema.SObjectField, fflib_SObjects2> domainsByRelationshipField)
|
||||
{
|
||||
Map<Id, Id> parentIdByChildId = getParentIdById(domainsByRelationshipField);
|
||||
|
||||
List<fflib_SObjects2> domains = domainsByRelationshipField.values();
|
||||
fflib_SObjects2 lastGrandParentDomain = domains.get(domains.size() - 1);
|
||||
|
||||
return fflib_ArrayUtils.replaceValue(
|
||||
parentIdByChildId,
|
||||
lastGrandParentDomain.getBooleanFieldByIdField(
|
||||
valueField,
|
||||
lastGrandParentDomain.getSObjectType().getDescribe().fields.getMap().get('Id')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a map between the record Ids of this domain to the last given grandparent domain.
|
||||
*
|
||||
@@ -129,11 +252,14 @@ public virtual class fflib_SObjects2
|
||||
*/
|
||||
public virtual Map<Id, Id> getParentIdById(Map<Schema.SObjectField, fflib_SObjects2> domainsByRelationshipField)
|
||||
{
|
||||
// Takes the highest related domain from the domainsByRelationshipField
|
||||
// Clone the domain map as we are about to modify it within this method
|
||||
Map<SObjectField, fflib_SObjects2> domains = domainsByRelationshipField.clone();
|
||||
|
||||
// Takes the highest related domain from the domains
|
||||
Schema.SObjectField relationshipField =
|
||||
new List<Schema.SObjectField>(domainsByRelationshipField.keySet())
|
||||
new List<Schema.SObjectField>(domains.keySet())
|
||||
.get(0);
|
||||
fflib_SObjects2 relatedDomain = domainsByRelationshipField.remove(relationshipField);
|
||||
fflib_SObjects2 relatedDomain = domains.remove(relationshipField);
|
||||
|
||||
// Extract the Id values of the related SObjectType
|
||||
Map<Id, Id> result = getIdFieldByIdField(
|
||||
@@ -141,10 +267,10 @@ public virtual class fflib_SObjects2
|
||||
getSObjectType().getDescribe().fields.getMap().get('Id'));
|
||||
|
||||
// Check if we need to resolve another parent relation, if not return the results
|
||||
if (domainsByRelationshipField.isEmpty()) return result;
|
||||
if (domains.isEmpty()) return result;
|
||||
|
||||
// Fetch the Ids of the related SObjectType
|
||||
Map<Id, Id> parentIdByChildId = relatedDomain.getParentIdById(domainsByRelationshipField);
|
||||
Map<Id, Id> parentIdByChildId = relatedDomain.getParentIdById(domains);
|
||||
|
||||
// Replace the maps value (ParentId) with the grand parent Id
|
||||
return fflib_ArrayUtils.replaceValue(result, parentIdByChildId);
|
||||
@@ -207,7 +333,7 @@ public virtual class fflib_SObjects2
|
||||
*/
|
||||
public virtual Map<Id, Set<Id>> getChildIdsById(Map<Schema.SObjectField, fflib_SObjects2> domainsByRelationshipField)
|
||||
{
|
||||
// Takes the highest related domain from the domainsByRelationshipField
|
||||
// Takes the highest related domain from the domains
|
||||
Schema.SObjectField relationshipField =
|
||||
new List<Schema.SObjectField>(domainsByRelationshipField.keySet())
|
||||
.get(0);
|
||||
@@ -552,6 +678,34 @@ public virtual class fflib_SObjects2
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map with the values of two fields.
|
||||
* Key fields containing null values are omitted
|
||||
*
|
||||
* @param valueField The field to use as the Value of the Map
|
||||
* @param keyField The Id field to use as the Key of the map
|
||||
*
|
||||
* @return Returns a map with the values of two fields
|
||||
*
|
||||
* @example
|
||||
* Accounts accounts = Accounts.newInstance(records);
|
||||
* Map<Id, Object> accountNameById = accounts.getFieldValueByIdField(Account.Name, Account.Id);
|
||||
*/
|
||||
@TestVisible
|
||||
protected virtual Map<Id, Object> getFieldValueByIdField(SObjectField valueField, SObjectField keyField) {
|
||||
Map<Id, Object> result = new Map<Id, Object>();
|
||||
for (SObject record : getRecords())
|
||||
{
|
||||
if (record.get(keyField) == null) continue;
|
||||
|
||||
result.put(
|
||||
(Id) record.get(keyField),
|
||||
record.get(valueField)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns only the first SObject contained in the domain.
|
||||
*/
|
||||
|
||||
@@ -373,6 +373,36 @@ public class fflib_ArrayUtils
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Object> replaceValue(Map<Id, Id> source, Map<Id, Object> replacement)
|
||||
{
|
||||
Map<Id, Object> result = new Map<Id, Object>();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes two maps and replaces the value of the source with the value of the replacement,
|
||||
* where the source value matches the replacement key.
|
||||
|
||||
@@ -257,6 +257,83 @@ private class fflib_SObjects2Test
|
||||
System.Assert.isNull(result.get(caseIdC), 'Case Id B should have no related Account Id');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldReturnTheRelatedParentStringById()
|
||||
{
|
||||
// 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, Name = 'Account Name', AnnualRevenue = 100.1);
|
||||
Contact contactRecordA = new Contact(Id = contactIdA, AccountId = accountId, DoNotCall = true);
|
||||
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, String> stringResult = caseDomain.getParentStringFieldById(
|
||||
Schema.Account.Name,
|
||||
new Map<Schema.SObjectField, fflib_SObjects2>
|
||||
{
|
||||
Schema.Case.ContactId => contactDomain,
|
||||
Schema.Contact.AccountId => accountDomain
|
||||
}
|
||||
);
|
||||
Map<Id, Decimal> decimalResult = caseDomain.getParentDecimalFieldById(
|
||||
Schema.Account.AnnualRevenue,
|
||||
new Map<Schema.SObjectField, fflib_SObjects2>
|
||||
{
|
||||
Schema.Case.ContactId => contactDomain,
|
||||
Schema.Contact.AccountId => accountDomain
|
||||
}
|
||||
);
|
||||
Map<Id, Boolean> booleanResult = caseDomain.getParentBooleanFieldById(
|
||||
Schema.Contact.DoNotCall,
|
||||
new Map<Schema.SObjectField, fflib_SObjects2>
|
||||
{
|
||||
Schema.Case.ContactId => contactDomain
|
||||
}
|
||||
);
|
||||
System.Test.stopTest();
|
||||
|
||||
// THEN it should return the AccountId By CaseId
|
||||
System.Assert.areEqual(3, stringResult.size(), 'Expected one related account to the case');
|
||||
System.Assert.isTrue(stringResult.containsKey(caseIdA));
|
||||
System.Assert.isTrue(stringResult.containsKey(caseIdB));
|
||||
System.Assert.isTrue(stringResult.containsKey(caseIdC));
|
||||
System.Assert.areEqual('Account Name', stringResult.get(caseIdA), 'Account Name should be related to the Case Id A');
|
||||
System.Assert.isNull(stringResult.get(caseIdB), 'Case Id B should have no related Account');
|
||||
System.Assert.isNull(stringResult.get(caseIdC), 'Case Id B should have no related Account');
|
||||
|
||||
System.Assert.areEqual(3, decimalResult.size(), 'Expected one related account to the case');
|
||||
System.Assert.isTrue(decimalResult.containsKey(caseIdA));
|
||||
System.Assert.isTrue(decimalResult.containsKey(caseIdB));
|
||||
System.Assert.isTrue(decimalResult.containsKey(caseIdC));
|
||||
System.Assert.areEqual(100.1, decimalResult.get(caseIdA), 'Account Name should be related to the Case Id A');
|
||||
System.Assert.isNull(decimalResult.get(caseIdB), 'Case Id B should have no related Account');
|
||||
System.Assert.isNull(decimalResult.get(caseIdC), 'Case Id B should have no related Account');
|
||||
|
||||
System.Assert.areEqual(3, booleanResult.size(), 'Expected one related account to the case');
|
||||
System.Assert.isTrue(booleanResult.containsKey(caseIdA));
|
||||
System.Assert.isTrue(booleanResult.containsKey(caseIdB));
|
||||
System.Assert.isTrue(booleanResult.containsKey(caseIdC));
|
||||
System.Assert.isTrue(booleanResult.get(caseIdA), 'Contact DoNotCall should be related to the Case Id A');
|
||||
System.Assert.isNull(booleanResult.get(caseIdB), 'Case Id B should have no related Contact');
|
||||
System.Assert.isNull(booleanResult.get(caseIdC), 'Case Id B should have no related Contact');
|
||||
}
|
||||
|
||||
/** Unit test for the deprecated method **/
|
||||
@IsTest
|
||||
static void itShouldReturnTheRelatedParentDomainId_deprecated()
|
||||
|
||||
Reference in New Issue
Block a user