Add mass copy fields feature (#80)

The new copyField method can copy field values in bulk from one domain to another related domain
This commit is contained in:
William Velzeboer
2026-01-27 14:25:16 +00:00
committed by GitHub
parent 09f0c2ef0d
commit 98c38f93b0
3 changed files with 165 additions and 19 deletions
@@ -734,17 +734,7 @@ public virtual class fflib_SObjects2
*/
@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 fflib_ArrayUtils.getValueById(this, valueField, keyField);
}
/**
@@ -1437,14 +1427,7 @@ public virtual class fflib_SObjects2
Schema.SObjectField sObjectFieldToUpdate,
Map<Id, Object> 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);
}
/**
@@ -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<Id, Object> accountNameById =
* fflib_ArrayUtils.copyFields(
* accounts,
* contacts,
* Contact.AccountId,
* new Map<SObjectField, SObjectField>{
* Account.BillingCity => Contact.MailingCity,
* Account.BillingCountry => Contact.MailingCountry
* });
*/
public static void copyFields(
fflib_ISObjects2 source,
fflib_ISObjects2 target,
SObjectField targetRelationShipField,
Map<SObjectField, SObjectField> targetBySourceField)
{
SObjectField sourceIdField = fflib_SObjectDescribe.getDescribe(source.getSObjectType()).getField('Id');
// Retrieve values
Map<Id, Map<SObjectField, Object>> sourceFieldValueById = new Map<Id, Map<SObjectField, Object>>();
for (SObject record : source.getRecords()) {
Map<SObjectField, Object> valueByField = new Map<SObjectField, Object>();
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<SObjectField, Object> 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<Id, Object> accountNameById = fflib_ArrayUtils.getValueById(accounts, Account.Name, Account.Id);
*/
public static Map<Id, Object> getValueById(fflib_ISObjects2 domain, SObjectField valueField, SObjectField keyField)
{
Map<Id, Object> result = new Map<Id, Object>();
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<Id, Object> 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
@@ -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<Account>{
accountRecord
});
Contact contactRecord = new Contact(
Id = fflib_IDGenerator.generate(Contact.SObjectType),
AccountId = accountRecord.Id
);
Contacts contacts = new Contacts(new List<Contact>{
contactRecord
});
System.Test.startTest();
fflib_ArrayUtils.copyFields(accounts, contacts, Contact.AccountId,
new Map<SObjectField, SObjectField>{
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<SObject> records) {
super(records, Account.SObjectType);
}
public override SObjectType getSObjectType() {
return Account.SObjectType;
}
}
private class Contacts extends fflib_SObjects2 {
public Contacts(List<SObject> records) {
super(records, Account.SObjectType);
}
public override SObjectType getSObjectType() {
return Contact.SObjectType;
}
}
}