Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dirty fields on upsert #2

Merged
merged 19 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ad4cfeb
Merge commit '07a3aafda9332ed74397bbec9319589bd3e0bec3' as 'subfolder…
rob-baillie-ortoo May 4, 2022
07a3aaf
Squashed 'subfolders/fflib/' content from commit 6eb7db0
rob-baillie-ortoo May 4, 2022
5c9d499
removed odd fflib addition again
rob-baillie-ortoo May 4, 2022
d421e30
Revert "removed fflib again"
rob-baillie-ortoo May 4, 2022
823a5cd
Put apex-mocks and apex-extensions into the fflib directory
rob-baillie-ortoo May 4, 2022
e9a712e
Removed external dependencies from the fflib extensions
rob-baillie-ortoo May 4, 2022
88b586b
Added reference to the fflib subfolder into the build
rob-baillie-ortoo May 4, 2022
51a3e94
Squashed 'subfolders/fflib/' changes from 6eb7db0..573efa3
rob-baillie-ortoo May 4, 2022
3ee369a
Merge commit '51a3e94a5328e9993db8a54f27ab7ccf958291d1' into feature/…
rob-baillie-ortoo May 4, 2022
e9ef8e8
moved subfolders over to subtrees
rob-baillie-ortoo May 4, 2022
3377da8
fixed deploy paths and swtiched from deploy to push
rob-baillie-ortoo May 4, 2022
2a42896
removed fflib so we can more it
rob-baillie-ortoo May 4, 2022
e028f6c
Merge commit '0d6360123fc8ef4b2aaae6c1565cc26fae81c291' as 'subtrees/…
rob-baillie-ortoo May 4, 2022
351819d
Added PoC of dirty fields added to registerNew and registerUpsert
rob-baillie-ortoo May 5, 2022
62bc894
Remove dirty fields from registerNew - don't think it ever makes sense
rob-baillie-ortoo May 5, 2022
627bb6f
Added tests for the new upsert with dirty fields
rob-baillie-ortoo May 5, 2022
9ea24f4
Added new upsert with dirty fields to the UOW interface and dynamic v…
rob-baillie-ortoo May 5, 2022
af9475b
Added missing mock methods
rob-baillie-ortoo May 5, 2022
22c1963
Fixed broken test and clarified behaviour in doc comments
rob-baillie-ortoo May 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ public interface fflib_ISObjectUnitOfWork
* If the records are previously registered as dirty, the dirty fields on the records in this call will overwrite
* the values of the previously registered dirty records
*
* @param records A list of existing records
* Note: you should avoid mixing SObject types when registering dirty fields
*
* @param records A list of existing records
* @param dirtyFields The fields to update if record is already registered
**/
void registerDirty(List<SObject> records, List<SObjectField> dirtyFields);
Expand Down Expand Up @@ -141,12 +143,30 @@ public interface fflib_ISObjectUnitOfWork
* @param record An new or existing record
**/
void registerUpsert(SObject record);
/**
* Register a new or existing record to be inserted/updated during the commitWork method, with a specific list of dirty fields to update / commit
*
* NOTE: If the record requires an insert, the dirtyFields list is ignored and ALL field values are included on the insert
*
* @param record A new or existing record
* @param dirtyFields A list of modified fields
**/
void registerUpsert( SObject record, List<SobjectField> dirtyFields );
/**
* Register a list of mix of new and existing records to be upserted during the commitWork method
*
* @param records A list of mix of existing and new records
**/
void registerUpsert(List<SObject> records);
/**
* Register a list of mix of new and existing records to be inserted updated during the commitWork method, with a specific list of dirty fields to update / commit
*
* NOTE: If a record requires an insert, the dirtyFields list is ignored and ALL field values are included on the insert
*
* @param records A list of mix of new and existing records
* @param dirtyFields A list of modified fields
**/
void registerUpsert( List<SObject> records, List<SobjectField> dirtyFields );
/**
* Register an existing record to be deleted during the commitWork method
*
Expand Down
54 changes: 49 additions & 5 deletions sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,10 @@ public virtual class fflib_SObjectUnitOfWork
}

/**
* Registers the entire records as dirty or just only the dirty fields if the record was already registered
* Registers the stated fields on the records as dirty
*
* Note: you should avoid mixing SObject types when registering dirty fields
*
* @param records SObjects to register as dirty
* @param dirtyFields A list of modified fields
*/
Expand All @@ -363,7 +365,7 @@ public virtual class fflib_SObjectUnitOfWork
}

/**
* Registers the entire record as dirty or just only the dirty fields if the record was already registered
* Registers the stated fields on the record as dirty
*
* @param record SObject to register as dirty
* @param dirtyFields A list of modified fields
Expand Down Expand Up @@ -395,9 +397,7 @@ public virtual class fflib_SObjectUnitOfWork
// Update the registered record's fields
SObject registeredRecord = m_dirtyMapByType.get(sObjectType).get(record.Id);

for (SObjectField dirtyField : dirtyFields) {
registeredRecord.put(dirtyField, record.get(dirtyField));
}
copyFields( record, registeredRecord, dirtyFields );

m_dirtyMapByType.get(sObjectType).put(record.Id, registeredRecord);
}
Expand Down Expand Up @@ -448,6 +448,26 @@ public virtual class fflib_SObjectUnitOfWork
}
}

/**
* Register a new or existing record to be inserted/updated during the commitWork method, with a specific list of dirty fields to update / commit
*
* NOTE: If the record requires an insert, the dirtyFields list is ignored and ALL field values are included on the insert
*
* @param record A new or existing record
* @param dirtyFields A list of modified fields
**/
public virtual void registerUpsert( SObject record, List<SobjectField> dirtyFields )
{
if ( record.Id == null )
{
registerNew( record, null, null );
}
else
{
registerDirty( record, dirtyFields );
}
}

/**
* Register a list of mix of new and existing records to be inserted updated during the commitWork method
*
Expand All @@ -461,6 +481,22 @@ public virtual class fflib_SObjectUnitOfWork
}
}

/**
* Register a list of mix of new and existing records to be inserted updated during the commitWork method, with a specific list of dirty fields to update / commit
*
* NOTE: If a record requires an insert, the dirtyFields list is ignored and ALL field values are included on the insert
*
* @param records A list of mix of new and existing records
* @param dirtyFields A list of modified fields
**/
public virtual void registerUpsert( List<SObject> records, List<SobjectField> dirtyFields )
{
for ( SObject record : records )
{
this.registerUpsert( record, dirtyFields );
}
}

/**
* Register an existing record to be deleted during the commitWork method
*
Expand Down Expand Up @@ -783,6 +819,14 @@ public virtual class fflib_SObjectUnitOfWork
}
}

private void copyFields( Sobject fromRecord, Sobject toRecord, List<SobjectField> fields )
{
for ( SObjectField thisField : fields )
{
toRecord.put( thisField, fromRecord.get( thisField ) );
}
}

private class Relationships
{
private List<IRelationship> m_relationships = new List<IRelationship>();
Expand Down
118 changes: 118 additions & 0 deletions sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,124 @@ private with sharing class fflib_SObjectUnitOfWorkTest
System.assertEquals(1, mockDML.recordsForInsert.size());
}

@isTest
private static void testRegisterUpsertForInsertWithDirtyFields() {

Opportunity newOpportunity = new Opportunity( Name = 'New Opportunity', StageName = 'Closed', CloseDate = System.today() );

Test.startTest();
MockDML mockDML = new MockDML();
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS, mockDML);
uow.registerUpsert( newOpportunity, new List<SobjectField>{ Opportunity.Name, Opportunity.StageName } );
uow.commitWork();
Test.stopTest();

System.assertEquals( 0, mockDML.recordsForUpdate.size() );
System.assertEquals( 1, mockDML.recordsForInsert.size() );

System.assert(
!new fflib_MatcherDefinitions.SObjectsWith(
new List<Map<SObjectField, Object>>{
new Map<SObjectField, Object>
{
Opportunity.Id => newOpportunity.Id,
Opportunity.Name => newOpportunity.Name,
Opportunity.StageName => newOpportunity.CloseDate
}
}
)
.matches( mockDML.recordsForInsert ),
'registerUpsert with dirty fields will ignore the dirty fields when sending them for insert'
);
}

@isTest
private static void testRegisterUpsertForUpdateWithDirtyFields() {

Opportunity updatedOpportunity = new Opportunity(
Id = fflib_IDGenerator.generate(Schema.Opportunity.SObjectType),
Name = 'Existing Opportunity',
StageName = 'Closed',
CloseDate = System.today());

Test.startTest();
MockDML mockDML = new MockDML();
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS, mockDML);
uow.registerUpsert( updatedOpportunity, new List<SobjectField>{ Opportunity.Name, Opportunity.StageName } );
uow.commitWork();
Test.stopTest();

System.assertEquals( 1, mockDML.recordsForUpdate.size() );
System.assertEquals( 0, mockDML.recordsForInsert.size() );

System.assert(
!new fflib_MatcherDefinitions.SObjectsWith(
new List<Map<SObjectField, Object>>{
new Map<SObjectField, Object>
{
Opportunity.Id => updatedOpportunity.Id,
Opportunity.Name => updatedOpportunity.Name,
Opportunity.StageName => ''
}
}
)
.matches( mockDML.recordsForUpdate ),
'registerUpsert with dirty fields will send the dirty fields when sending the record them for update'
);
}

@isTest
private static void testRegisterUpsertWithMultipleRecordsAndDirtyFields() {

Opportunity updatedOpportunity = new Opportunity(
Id = fflib_IDGenerator.generate(Schema.Opportunity.SObjectType),
Name = 'Existing Opportunity',
StageName = 'Closed',
CloseDate = System.today());

Opportunity newOpportunity = new Opportunity( Name = 'New Opportunity', StageName = 'Closed', CloseDate = System.today() );

Test.startTest();
MockDML mockDML = new MockDML();
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS, mockDML);
uow.registerUpsert( new List<Opportunity>{ updatedOpportunity, newOpportunity }, new List<SobjectField>{ Opportunity.Name, Opportunity.StageName } );
uow.commitWork();
Test.stopTest();

System.assertEquals(1, mockDML.recordsForUpdate.size());
System.assertEquals(1, mockDML.recordsForInsert.size());

System.assert(
!new fflib_MatcherDefinitions.SObjectsWith(
new List<Map<SObjectField, Object>>{
new Map<SObjectField, Object>
{
Opportunity.Id => newOpportunity.Id,
Opportunity.Name => newOpportunity.Name,
Opportunity.StageName => newOpportunity.CloseDate
}
}
)
.matches( mockDML.recordsForInsert ),
'registerUpsert with dirty fields will ignore the dirty fields when sending them for insert'
);

System.assert(
!new fflib_MatcherDefinitions.SObjectsWith(
new List<Map<SObjectField, Object>>{
new Map<SObjectField, Object>
{
Opportunity.Id => updatedOpportunity.Id,
Opportunity.Name => updatedOpportunity.Name,
Opportunity.StageName => ''
}
}
)
.matches( mockDML.recordsForUpdate ),
'registerUpsert with dirty fields will send the dirty fields when sending the record them for update'
);
}

/**
* Assert that actual events exactly match expected events (size, order and name)
* and types match expected types
Expand Down
10 changes: 10 additions & 0 deletions sfdx-source/apex-common/test/classes/mocks/fflib_SObjectMocks.cls
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ public class fflib_SObjectMocks
mocks.mockVoidMethod(this, 'registerUpsert', new List<Type> {List<SObject>.class}, new List<Object> {record});
}

public void registerUpsert(SObject record, List<SobjectField> dirtyFields )
{
mocks.mockVoidMethod(this, 'registerUpsert', new List<Type> {List<SObject>.class}, new List<Object> {record, dirtyFields});
}

public void registerEmptyRecycleBin(SObject record)
{
mocks.mockVoidMethod(this, 'registerEmptyRecycleBin', new List<Type> {List<SObject>.class}, new List<Object> {record});
Expand All @@ -170,6 +175,11 @@ public class fflib_SObjectMocks
mocks.mockVoidMethod(this, 'registerUpsert', new List<Type> {List<SObject>.class}, new List<Object> {records});
}

public void registerUpsert(List<SObject> records, List<SobjectField> dirtyFields )
{
mocks.mockVoidMethod(this, 'registerUpsert', new List<Type> {List<SObject>.class}, new List<Object> {records, dirtyFields});
}

public void registerDeleted(SObject record)
{
mocks.mockVoidMethod(this, 'registerDeleted', new List<Type> {SObject.class}, new List<Object> {record});
Expand Down