From 98c38f93b0da51951174bfb0fd7a81e5ee205985 Mon Sep 17 00:00:00 2001 From: William Velzeboer <6429417+wimvelzeboer@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:25:16 +0000 Subject: [PATCH] Add mass copy fields feature (#80) The new copyField method can copy field values in bulk from one domain to another related domain --- .../classes/domains/fflib_SObjects2.cls | 21 +--- .../classes/utils/fflib_ArrayUtils.cls | 109 ++++++++++++++++++ .../classes/utils/fflib_ArrayUtilsTest.cls | 54 +++++++++ 3 files changed, 165 insertions(+), 19 deletions(-) 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 77348ed..dcbb89b 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 @@ -734,17 +734,7 @@ public virtual class fflib_SObjects2 */ @TestVisible protected virtual Map getFieldValueByIdField(SObjectField valueField, SObjectField keyField) { - Map result = new Map(); - for (SObject record : getRecords()) - { - if (record.get(keyField) == null) continue; - - result.put( - (Id) record.get(keyField), - record.get(valueField) - ); - } - return result; + return fflib_ArrayUtils.getValueById(this, valueField, keyField); } /** @@ -1437,14 +1427,7 @@ public virtual class fflib_SObjects2 Schema.SObjectField sObjectFieldToUpdate, Map values) { - for (SObject record : getRecords()) - { - Id keyValue = (Id) record.get(sObjectIdFieldToCheck); - if (values?.containsKey(keyValue)) - { - record.put(sObjectFieldToUpdate, values.get(keyValue)); - } - } + fflib_ArrayUtils.setFieldValue(this, sObjectIdFieldToCheck, sObjectFieldToUpdate, values); } /** 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 0736592..22586ab 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 @@ -59,6 +59,62 @@ public class fflib_ArrayUtils return result; } + /** + * Copies the given fields from the a domain to another domain + * + * @param source The domain holding the values that need to be copied over to the target + * @param target The target domain that should be updated + * @param targetRelationShipField The relationship field on the target linking to the source record + * @param targetBySourceField The target SObjectField by the source SObjectField + * + * @example + * Accounts accounts = Accounts.newInstance(accountRecords); + * Contacts contact = Contact.newInstance(contactRecords + * Map accountNameById = + * fflib_ArrayUtils.copyFields( + * accounts, + * contacts, + * Contact.AccountId, + * new Map{ + * Account.BillingCity => Contact.MailingCity, + * Account.BillingCountry => Contact.MailingCountry + * }); + */ + public static void copyFields( + fflib_ISObjects2 source, + fflib_ISObjects2 target, + SObjectField targetRelationShipField, + Map targetBySourceField) + { + SObjectField sourceIdField = fflib_SObjectDescribe.getDescribe(source.getSObjectType()).getField('Id'); + + // Retrieve values + Map> sourceFieldValueById = new Map>(); + for (SObject record : source.getRecords()) { + + Map valueByField = new Map(); + for (SObjectField sourceField : targetBySourceField.keySet()) { + valueByField.put(sourceField, record.get(sourceField)); + } + sourceFieldValueById.put((Id) record.get(sourceIdField), valueByField); + } + + // Set values + for (SObject record : target.getRecords()) { + Id recordId = (Id) record.get(targetRelationShipField); + if (sourceFieldValueById.containsKey(recordId) == false) continue; + + Map valueByField = sourceFieldValueById.get(recordId); + for (SObjectField sourceField : valueByField.keySet()) { + if (targetBySourceField.containsKey(sourceField) == false) continue; + + SObjectField targetField = targetBySourceField.get(sourceField); + + record.put(targetField, valueByField.get(sourceField)); + } + } + } + /** * Gets the first value of the list * @@ -93,6 +149,35 @@ public class fflib_ArrayUtils return result; } + /** + * Get a map with the values of two fields. + * Key fields containing null values are omitted + * + * @param domain The domain holding the records + * @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 accountNameById = fflib_ArrayUtils.getValueById(accounts, Account.Name, Account.Id); + */ + public static Map getValueById(fflib_ISObjects2 domain, SObjectField valueField, SObjectField keyField) + { + Map result = new Map(); + for (SObject record : domain.getRecords()) + { + if (record.get(keyField) == null) continue; + + result.put( + (Id) record.get(keyField), + record.get(valueField) + ); + } + return result; + } + /** * Validates if a list is empty * @@ -625,6 +710,30 @@ public class fflib_ArrayUtils return sObjects; } + /** + * Sets a value to the given field only when key field Id value is provided in the given map + * + * @param domain The domain containing the records to update + * @param sObjectIdFieldToCheck The SObject Id Field to match the key against in the provided map + * @param sObjectFieldToUpdate The SObjectField to store the mapped value when the key matches the value in the sObjectFieldToUpdate field + * @param values Map of values to store by the sObjectIdFieldToCheck fields value + */ + public static void setFieldValue( + fflib_ISObjects2 domain, + Schema.SObjectField sObjectIdFieldToCheck, + Schema.SObjectField sObjectFieldToUpdate, + Map values) + { + for (SObject record : domain.getRecords()) + { + Id keyValue = (Id) record.get(sObjectIdFieldToCheck); + if (values.containsKey(keyValue)) + { + record.put(sObjectFieldToUpdate, values.get(keyValue)); + } + } + } + /** * Converts all String of the list into uppercase * null values are ignored 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 374c180..d1c6ad8 100644 --- a/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls +++ b/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls @@ -30,6 +30,40 @@ private class fflib_ArrayUtilsTest ); } + @IsTest + static void itShouldCopyFields() { + final String city = 'New York'; + final String country = 'United States'; + + Account accountRecord = new Account( + Id = fflib_IDGenerator.generate(Account.SObjectType), + BillingCity = city, + BillingCountry = country); + Accounts accounts = new Accounts(new List{ + accountRecord + }); + + Contact contactRecord = new Contact( + Id = fflib_IDGenerator.generate(Contact.SObjectType), + AccountId = accountRecord.Id + ); + Contacts contacts = new Contacts(new List{ + contactRecord + }); + + System.Test.startTest(); + fflib_ArrayUtils.copyFields(accounts, contacts, Contact.AccountId, + new Map{ + Account.BillingCity => Contact.MailingCity, + Account.BillingCountry => Contact.MailingCountry + } + ); + System.Test.stopTest(); + + System.Assert.areEqual(city, contactRecord.MailingCity, 'Mailing City not Copied'); + System.Assert.areEqual(country, contactRecord.MailingCountry, 'Mailing Country not Copied'); + } + @IsTest static void itShouldReturnEmptyListOnEmptyObjectToString() { @@ -499,4 +533,24 @@ private class fflib_ArrayUtilsTest Datetime.newInstance(2018, 07, 07, 07, 7, 7) }; } + + private class Accounts extends fflib_SObjects2 { + public Accounts(List records) { + super(records, Account.SObjectType); + } + + public override SObjectType getSObjectType() { + return Account.SObjectType; + } + } + + private class Contacts extends fflib_SObjects2 { + public Contacts(List records) { + super(records, Account.SObjectType); + } + + public override SObjectType getSObjectType() { + return Contact.SObjectType; + } + } } \ No newline at end of file