Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 25 additions & 7 deletions fflib/src/classes/fflib_ISObjectUnitOfWork.cls
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ public interface fflib_ISObjectUnitOfWork
* Register a newly created SObject instance to be inserted when commitWork is called
*
* @param record A newly created SObject instance to be inserted during commitWork
**/
**/
void registerNew(SObject record);
/**
* Register a list of newly created SObject instances to be inserted when commitWork is called
*
* @param records A list of newly created SObject instances to be inserted during commitWork
**/
void registerNew(List<SObject> records);
/**
* Register a newly created SObject instance to be inserted when commitWork is called,
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
*
* @param record A newly created SObject instance to be inserted during commitWork
* @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent
* @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separatly)
**/
**/
void registerNew(SObject record, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord);
/**
* Register a relationship between two records that have yet to be inserted to the database. This information will be
Expand All @@ -36,22 +42,34 @@ public interface fflib_ISObjectUnitOfWork
* @param record An existing or newly created record
* @param relatedToField A SObjectField referene to the lookup field that relates the two records together
* @param relatedTo A SOBject instance (yet to be commited to the database)
*/
*/
void registerRelationship(SObject record, Schema.sObjectField relatedToField, SObject relatedTo);
/**
* Register an existing record to be updated during the commitWork method
*
* @param record An existing record
**/
**/
void registerDirty(SObject record);
/**
* Register a list of existing records to be updated during the commitWork method
*
* @param records A list of existing records
**/
void registerDirty(List<SObject> records);
/**
* Register an existing record to be deleted during the commitWork method
*
* @param record An existing record
**/
**/
void registerDeleted(SObject record);
/**
* Register a list of existing records to be deleted during the commitWork method
*
* @param records A list of existing records
**/
void registerDeleted(List<SObject> records);
/**
* Takes all the work that has been registered with the UnitOfWork and commits it to the database
**/
void commitWork();
**/
void commitWork();
}
39 changes: 39 additions & 0 deletions fflib/src/classes/fflib_SObjectUnitOfWork.cls
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ public virtual class fflib_SObjectUnitOfWork
registerNew(record, null, null);
}

/**
* Register a list of newly created SObject instances to be inserted when commitWork is called
*
* @param records A list of newly created SObject instances to be inserted during commitWork
**/
public void registerNew(List<SObject> records)
{
for(SObject record : records)
{
registerNew(record, null, null);
}
}

/**
* Register a newly created SObject instance to be inserted when commitWork is called,
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
Expand Down Expand Up @@ -202,6 +215,19 @@ public virtual class fflib_SObjectUnitOfWork
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
}

/**
* Register a list of existing records to be updated during the commitWork method
*
* @param records A list of existing records
**/
public void registerDirty(List<SObject> records)
{
for(SObject record : record)
{
this.registerDirty(record);
}
}

/**
* Register an existing record to be deleted during the commitWork method
*
Expand All @@ -217,6 +243,19 @@ public virtual class fflib_SObjectUnitOfWork
m_deletedMapByType.get(sObjectType).put(record.Id, record);
}

/**
* Register a list of existing records to be deleted during the commitWork method
*
* @param records A list of existing records
**/
public void registerDeleted(List<SObject> records)
{
for(SObject record : records)
{
this.registerDeleted(record);
}
}

/**
* Takes all the work that has been registered with the UnitOfWork and commits it to the database
**/
Expand Down
70 changes: 35 additions & 35 deletions fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -40,40 +40,40 @@ private with sharing class fflib_SObjectUnitOfWorkTest
{
// Insert Opporunities with UnitOfWork
{
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS);
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS);
for(Integer o=0; o<10; o++)
{
Opportunity opp = new Opportunity();
opp.Name = 'UoW Test Name ' + o;
opp.StageName = 'Open';
opp.CloseDate = System.today();
uow.registerNew(opp);
uow.registerNew(new List<SObject>{opp});
for(Integer i=0; i<o+1; i++)
{
{
Product2 product = new Product2();
product.Name = opp.Name + ' : Product : ' + i;
uow.registerNew(product);
uow.registerNew(new List<SObject>{product});
PricebookEntry pbe = new PricebookEntry();
pbe.UnitPrice = 10;
pbe.IsActive = true;
pbe.UseStandardPrice = false;
pbe.Pricebook2Id = Test.getStandardPricebookId();
uow.registerNew(pbe, PricebookEntry.Product2Id, product);
uow.registerNew(pbe, PricebookEntry.Product2Id, product);
OpportunityLineItem oppLineItem = new OpportunityLineItem();
oppLineItem.Quantity = 1;
oppLineItem.TotalPrice = 10;
uow.registerRelationship(oppLineItem, OpportunityLineItem.PricebookEntryId, pbe);
uow.registerNew(oppLineItem, OpportunityLineItem.OpportunityId, opp);
}
}
}
uow.commitWork();
}

// Assert Results
assertResults('UoW');
// TODO: Need to re-instate this check with a better approach, as it is not possible when
// product triggers contribute to DML (e.g. in sample app Opportunity trigger)
// System.assertEquals(5 /* Oddly a setSavePoint consumes a DML */, Limits.getDmlStatements());
// System.assertEquals(5 /* Oddly a setSavePoint consumes a DML */, Limits.getDmlStatements());

// Records to update
List<Opportunity> opps = [select Id, Name, (Select Id from OpportunityLineItems) from Opportunity where Name like 'UoW Test Name %' order by Name];
Expand All @@ -82,11 +82,11 @@ private with sharing class fflib_SObjectUnitOfWorkTest
{
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS);
Opportunity opp = opps[0];
opp.Name = opp.Name + ' Changed';
uow.registerDirty(opp);
opp.Name = opp.Name + ' Changed';
uow.registerDirty(new List<SObject>{opp});
Product2 product = new Product2();
product.Name = opp.Name + ' : New Product';
uow.registerNew(product);
uow.registerNew(new List<SObject>{product});
PricebookEntry pbe = new PricebookEntry();
pbe.UnitPrice = 10;
pbe.IsActive = true;
Expand All @@ -97,21 +97,21 @@ private with sharing class fflib_SObjectUnitOfWorkTest
newOppLineItem.Quantity = 1;
newOppLineItem.TotalPrice = 10;
uow.registerRelationship(newOppLineItem, OpportunityLineItem.PricebookEntryId, pbe);
uow.registerNew(newOppLineItem, OpportunityLineItem.OpportunityId, opp);
uow.registerNew(newOppLineItem, OpportunityLineItem.OpportunityId, opp);
OpportunityLineItem existingOppLine = opp.OpportunityLineItems[0];
// Test that operations on the same object can be daisy chained, and the same object registered as dirty more than once
// This verifies that using a Map to back the dirty records collection prevents duplicate registration.
existingOppLine.Quantity = 2;
uow.registerDirty(existingOppLine);
uow.registerDirty(new List<SObject>{existingOppLine});
existingOppLine.TotalPrice = 20;
uow.registerDirty(existingOppLine);
uow.registerDirty(new List<SObject>{existingOppLine});
uow.commitWork();
}

// Assert Results
// TODO: Need to re-instate this check with a better approach, as it is not possible when
// product triggers contribute to DML (e.g. in sample app Opportunity trigger)
// System.assertEquals(11, Limits.getDmlStatements());
// product triggers contribute to DML (e.g. in sample app Opportunity trigger)
// System.assertEquals(11, Limits.getDmlStatements());
opps = [select Id, Name, (Select Id, PricebookEntry.Product2.Name, Quantity, TotalPrice from OpportunityLineItems Order By PricebookEntry.Product2.Name) from Opportunity where Name like 'UoW Test Name %' order by Name];
System.assertEquals(10, opps.size());
System.assertEquals('UoW Test Name 0 Changed', opps[0].Name);
Expand All @@ -124,27 +124,27 @@ private with sharing class fflib_SObjectUnitOfWorkTest
// Delete some records with the UnitOfWork
{
fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS);
uow.registerDeleted(opps[0].OpportunityLineItems[1].PricebookEntry.Product2); // Delete PricebookEntry Product
uow.registerDeleted(opps[0].OpportunityLineItems[1].PricebookEntry); // Delete PricebookEntry
uow.registerDeleted(opps[0].OpportunityLineItems[1]); // Delete OpportunityLine Item
uow.registerDeleted(new List<SObject>{opps[0].OpportunityLineItems[1].PricebookEntry.Product2}); // Delete PricebookEntry Product
uow.registerDeleted(new List<SObject>{opps[0].OpportunityLineItems[1].PricebookEntry}); // Delete PricebookEntry
uow.registerDeleted(new List<SObject>{opps[0].OpportunityLineItems[1]}); // Delete OpportunityLine Item
// Register the same deletions more than once.
// This verifies that using a Map to back the deleted records collection prevents duplicate registration.
uow.registerDeleted(opps[0].OpportunityLineItems[1].PricebookEntry.Product2); // Delete PricebookEntry Product
uow.registerDeleted(opps[0].OpportunityLineItems[1].PricebookEntry); // Delete PricebookEntry
uow.registerDeleted(opps[0].OpportunityLineItems[1]); // Delete OpportunityLine Item
uow.registerDeleted(new List<SObject>{opps[0].OpportunityLineItems[1].PricebookEntry.Product2}); // Delete PricebookEntry Product
uow.registerDeleted(new List<SObject>{opps[0].OpportunityLineItems[1].PricebookEntry}); // Delete PricebookEntry
uow.registerDeleted(new List<SObject>{opps[0].OpportunityLineItems[1]}); // Delete OpportunityLine Item
uow.commitWork();
}
}

// Assert Results
// TODO: Need to re-instate this check with a better approach, as it is not possible when
// product triggers contribute to DML (e.g. in sample app Opportunity trigger)
// System.assertEquals(15, Limits.getDmlStatements());
// product triggers contribute to DML (e.g. in sample app Opportunity trigger)
// System.assertEquals(15, Limits.getDmlStatements());
opps = [select Id, Name, (Select Id, PricebookEntry.Product2.Name, Quantity from OpportunityLineItems Order By PricebookEntry.Product2.Name) from Opportunity where Name like 'UoW Test Name %' order by Name];
List<Product2> prods = [Select Id from Product2 where Name = 'UoW Test Name 0 Changed : New Product'];
System.assertEquals(10, opps.size());
System.assertEquals('UoW Test Name 0 Changed', opps[0].Name);
System.assertEquals(1, opps[0].OpportunityLineItems.size()); // Should have deleted OpportunityLineItem added above
System.assertEquals(0, prods.size()); // Should have deleted Product added above
System.assertEquals(0, prods.size()); // Should have deleted Product added above
}

private static void assertResults(String prefix)
Expand All @@ -153,15 +153,15 @@ private with sharing class fflib_SObjectUnitOfWorkTest
String filter = prefix + ' Test Name %';
List<Opportunity> opps = [select Id, Name, (Select Id from OpportunityLineItems) from Opportunity where Name like :filter order by Name];
System.assertEquals(10, opps.size());
System.assertEquals(1, opps[0].OpportunityLineItems.size());
System.assertEquals(2, opps[1].OpportunityLineItems.size());
System.assertEquals(3, opps[2].OpportunityLineItems.size());
System.assertEquals(4, opps[3].OpportunityLineItems.size());
System.assertEquals(5, opps[4].OpportunityLineItems.size());
System.assertEquals(6, opps[5].OpportunityLineItems.size());
System.assertEquals(7, opps[6].OpportunityLineItems.size());
System.assertEquals(8, opps[7].OpportunityLineItems.size());
System.assertEquals(9, opps[8].OpportunityLineItems.size());
System.assertEquals(10, opps[9].OpportunityLineItems.size());
System.assertEquals(1, opps[0].OpportunityLineItems.size());
System.assertEquals(2, opps[1].OpportunityLineItems.size());
System.assertEquals(3, opps[2].OpportunityLineItems.size());
System.assertEquals(4, opps[3].OpportunityLineItems.size());
System.assertEquals(5, opps[4].OpportunityLineItems.size());
System.assertEquals(6, opps[5].OpportunityLineItems.size());
System.assertEquals(7, opps[6].OpportunityLineItems.size());
System.assertEquals(8, opps[7].OpportunityLineItems.size());
System.assertEquals(9, opps[8].OpportunityLineItems.size());
System.assertEquals(10, opps[9].OpportunityLineItems.size());
}
}