From 19edc332448c3a2855264ef04b7a68b0bb16fc73 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 17 Jul 2018 23:56:19 -0700 Subject: [PATCH 1/8] support reflective lookups and setting lookups when parent is inserted before child --- fflib/src/classes/fflib_SObjectUnitOfWork.cls | 159 +++++++++++++++++- .../classes/fflib_SObjectUnitOfWorkTest.cls | 71 ++++++++ 2 files changed, 224 insertions(+), 6 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectUnitOfWork.cls b/fflib/src/classes/fflib_SObjectUnitOfWork.cls index dd5030ce27b..775d295147b 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWork.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWork.cls @@ -70,6 +70,8 @@ public virtual class fflib_SObjectUnitOfWork protected IDML m_dml; + protected Boolean m_attemptToResolveOutOfOrderRelationships = false; + /** * Interface describes work to be performed during the commitWork method **/ @@ -135,6 +137,22 @@ public virtual class fflib_SObjectUnitOfWork public virtual void onCommitWorkFinishing() {} public virtual void onCommitWorkFinished(Boolean wasSuccessful) {} + /** + * Calling this allows relationships class to track relationships that weren't resolved on insert and attempt + * to set the relationship in a subsequent update. This is slightly less efficient than the default behaviour + * of silently failing to set the lookup in this scenario. + * + * @return this unit of work + */ + public fflib_SObjectUnitOfWork attemptToResolveOutOfOrderRelationships() + { + m_attemptToResolveOutOfOrderRelationships = true; + for (Relationships relationships : m_relationships.values()) { + relationships.attemptToResolveOutOfOrderRelationships(true); + } + return this; + } + /** * Registers the type to be used for DML operations * @@ -147,7 +165,8 @@ public virtual class fflib_SObjectUnitOfWork m_newListByType.put(sObjectType.getDescribe().getName(), new List()); m_dirtyMapByType.put(sObjectType.getDescribe().getName(), new Map()); m_deletedMapByType.put(sObjectType.getDescribe().getName(), new Map()); - m_relationships.put(sObjectType.getDescribe().getName(), new Relationships()); + m_relationships.put(sObjectType.getDescribe().getName(), + new Relationships().attemptToResolveOutOfOrderRelationships(m_attemptToResolveOutOfOrderRelationships)); // give derived class opportunity to register the type onRegisterType(sObjectType); @@ -360,6 +379,21 @@ public virtual class fflib_SObjectUnitOfWork m_relationships.get(sObjectType.getDescribe().getName()).resolve(); m_dml.dmlInsert(m_newListByType.get(sObjectType.getDescribe().getName())); } + + // Resolve any unresolved relationships where parent was inserted after child, and so child lookup was not set + if (m_attemptToResolveOutOfOrderRelationships) + { + for(Schema.SObjectType sObjectType : m_sObjectTypes) + { + Relationships relationships = m_relationships.get(sObjectType.getDescribe().getName()); + if (relationships.hasParentInsertedAfterChild()) + { + List childrenToUpdate = relationships.resolveParentInsertedAfterChild(); + m_dml.dmlUpdate(childrenToUpdate); + } + } + } + // Update by type for(Schema.SObjectType sObjectType : m_sObjectTypes) m_dml.dmlUpdate(m_dirtyMapByType.get(sObjectType.getDescribe().getName()).values()); @@ -405,6 +439,24 @@ public virtual class fflib_SObjectUnitOfWork private class Relationships { private List m_relationships = new List(); + private Boolean m_attemptToResolveOutOfOrderRelationships = false; + private List m_parentInsertedAfterChildRelationships = + new List(); + + /** + * Calling this allows relationships class to track relationships that weren't resolved on insert and attempt + * to set the relationship in a subsequent update. This is slightly less efficient than the default behaviour + * of silently failing to set the lookup in this scenario. + * + * @param attemptToResolve If true then will track relationships that weren't resolved on insert + * + * @return this object + */ + public Relationships attemptToResolveOutOfOrderRelationships(Boolean attemptToResolve) + { + m_attemptToResolveOutOfOrderRelationships = attemptToResolve; + return this; + } public void resolve() { @@ -413,18 +465,85 @@ public virtual class fflib_SObjectUnitOfWork { //relationship.Record.put(relationship.RelatedToField, relationship.RelatedTo.Id); relationship.resolve(); + + // Check if parent is inserted after the child + if (m_attemptToResolveOutOfOrderRelationships && + relationship instanceof RelationshipPermittingOutOfOrderInsert && + !((RelationshipPermittingOutOfOrderInsert) relationship).Resolved) + { + m_parentInsertedAfterChildRelationships.add((RelationshipPermittingOutOfOrderInsert) relationship); + } } + } + /** + * @return true if there are unresolved relationships + */ + public Boolean hasParentInsertedAfterChild() + { + return !m_parentInsertedAfterChildRelationships.isEmpty(); + } + + /** + * Call this after all records in the UOW have been inserted to set the lookups on the children that were + * inserted before the parent was inserted + * + * @throws UnitOfWorkException if the parent still does not have an ID - can occur if parent is not registered + * @return The child records to update in order to set the lookups + */ + public List resolveParentInsertedAfterChild() { + for (RelationshipPermittingOutOfOrderInsert relationship : m_parentInsertedAfterChildRelationships) + { + relationship.resolve(); + if (!relationship.Resolved) + { + throw new UnitOfWorkException('Error resolving relationship where parent is inserted after child.' + + ' The parent has not been inserted. Is it registered with a unit of work?'); + } + } + return getChildRecordsWithParentInsertedAfter(); + } + + /** + * Call after calling resolveParentInsertedAfterChild() + * + * @return The child records to update in order to set the lookups + */ + private List getChildRecordsWithParentInsertedAfter() + { + // Get rid of dupes + Map recordsToUpdate = new Map(); + for (RelationshipPermittingOutOfOrderInsert relationship : m_parentInsertedAfterChildRelationships) + { + SObject childRecord = relationship.Record; + SObject recordToUpdate = recordsToUpdate.get(childRecord.Id); + if (recordToUpdate == null) + recordToUpdate = childRecord.getSObjectType().newSObject(childRecord.Id); + recordToUpdate.put(relationship.RelatedToField, childRecord.get(relationship.RelatedToField)); + recordsToUpdate.put(recordToUpdate.Id, recordToUpdate); + } + return recordsToUpdate.values(); } public void add(SObject record, Schema.sObjectField relatedToField, SObject relatedTo) { // Relationship to resolve - Relationship relationship = new Relationship(); - relationship.Record = record; - relationship.RelatedToField = relatedToField; - relationship.RelatedTo = relatedTo; - m_relationships.add(relationship); + if (!m_attemptToResolveOutOfOrderRelationships) + { + Relationship relationship = new Relationship(); + relationship.Record = record; + relationship.RelatedToField = relatedToField; + relationship.RelatedTo = relatedTo; + m_relationships.add(relationship); + } + else + { + RelationshipPermittingOutOfOrderInsert relationship = new RelationshipPermittingOutOfOrderInsert(); + relationship.Record = record; + relationship.RelatedToField = relatedToField; + relationship.RelatedTo = relatedTo; + m_relationships.add(relationship); + } } public void add(Messaging.SingleEmailMessage email, SObject relatedTo) @@ -453,6 +572,34 @@ public virtual class fflib_SObjectUnitOfWork } } + private class RelationshipPermittingOutOfOrderInsert implements IRelationship { + public SObject Record; + public Schema.sObjectField RelatedToField; + public SObject RelatedTo; + public Boolean Resolved = false; + + public void resolve() + { + if (RelatedTo.Id == null) { + /* + If relationship is between two records in same table then update is always required to set the lookup, + so no warning is needed. Otherwise the caller may be able to be more efficient by reordering the order + that the records are inserted, so alert the caller of this. + */ + if (RelatedTo.getSObjectType() != Record.getSObjectType()) { + System.debug(System.LoggingLevel.WARN, 'Inefficient use of register relationship, related to ' + + 'record should be first in dependency list to save an update; parent should be inserted ' + + 'before child so child does not need an update. In unit of work initialization put ' + + '' + RelatedTo.getSObjectType() + ' before ' + Record.getSObjectType()); + } + resolved = false; + } else { + Record.put(RelatedToField, RelatedTo.Id); + resolved = true; + } + } + } + private class EmailRelationship implements IRelationship { public Messaging.SingleEmailMessage email; diff --git a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls index b4c008b4baf..a01b6fb239b 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls @@ -35,6 +35,77 @@ private with sharing class fflib_SObjectUnitOfWorkTest Opportunity.SObjectType, OpportunityLineItem.SObjectType }; + @isTest + private static void testDoNotSupportOutOfOrderRelationships() { + // Insert contacts before accounts + List dependencyOrder = + new Schema.SObjectType[] { + Contact.SObjectType, + Account.SObjectType + }; + + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder); + List contacts = new List(); + for(Integer i=0; i<10; i++) + { + Account acc = new Account(Name = 'Account ' + i); + uow.registerNew(new List{acc}); + Contact cont = new Contact(LastName='Contact ' + i); + contacts.add(cont); + uow.registerNew(cont, Contact.AccountId, acc); + } + + uow.commitWork(); + + // Assert that the lookups were not set (default behaviour) + contacts = [ + SELECT AccountId + FROM Contact + WHERE Id IN :contacts + ]; + for (Contact cont : contacts) { + System.assertEquals(null, cont.AccountId); + } + } + @isTest + private static void testSupportOutOfOrderRelationships() { + // Insert contacts before accounts + List dependencyOrder = + new Schema.SObjectType[] { + Contact.SObjectType, + Account.SObjectType + }; + + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder) + .attemptToResolveOutOfOrderRelationships(); + List accounts = new List(); + List contacts = new List(); + for(Integer i=0; i<10; i++) + { + Account acc = new Account(Name = 'Account ' + i); + uow.registerNew(new List{acc}); + accounts.add(acc); + Contact cont = new Contact(LastName='Contact ' + i); + contacts.add(cont); + uow.registerNew(cont, Contact.AccountId, acc); + } + + uow.commitWork(); + + // Assert that the lookups were set + Map contactMap = new Map ([ + SELECT AccountId + FROM Contact + WHERE Id IN :contacts + ]); + + for (Integer i = 0; i < 10; i++) { + Contact cont = contacts[i]; + Account acc = accounts[i]; + System.assertEquals(acc.Id, contactMap.get(cont.Id).AccountId); + } + } + @isTest private static void testUnitOfWorkEmail() { From 0a5c8b676bf286ab37982f1e9eb49de80c4bd18f Mon Sep 17 00:00:00 2001 From: John Rogers Date: Thu, 27 Dec 2018 15:23:42 -0800 Subject: [PATCH 2/8] resolve out of order relationships is static throughout life of unit of work --- fflib/src/classes/fflib_SObjectUnitOfWork.cls | 39 ++++++++++++++++++- .../classes/fflib_SObjectUnitOfWorkTest.cls | 3 +- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectUnitOfWork.cls b/fflib/src/classes/fflib_SObjectUnitOfWork.cls index 775d295147b..ba03ea68d54 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWork.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWork.cls @@ -109,8 +109,41 @@ public virtual class fflib_SObjectUnitOfWork this(sObjectTypes,new SimpleDML()); } + /** + * Constructs a new UnitOfWork to support work against the given object list + * + * @param sObjectTypes A list of objects given in dependency order (least dependent first) + * @param resolveOutOfOrderRelationships If true and a relationship is registered where a parent is inserted after a + * child then will update the child post-insert to set the relationship. If + * false then relationship will not be set. + */ + public fflib_SObjectUnitOfWork(List sObjectTypes, Boolean resolveOutOfOrderRelationships) { + this(sObjectTypes, new SimpleDML(), resolveOutOfOrderRelationships); + } + /** + * Constructs a new UnitOfWork to support work against the given object list + * + * @param sObjectTypes A list of objects given in dependency order (least dependent first) + * @param resolveOutOfOrderRelationships If true and a relationship is registered where a parent is inserted after a + * child then will update the child post-insert to set the relationship. If + * false then relationship will not be set. + */ public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml) + { + this(sObjectTypes, dml, false); + } + + /** + * Constructs a new UnitOfWork to support work against the given object list + * + * @param sObjectTypes A list of objects given in dependency order (least dependent first) + * @param dml Modify the database via this class + * @param resolveOutOfOrderRelationships If true and a relationship is registered where a parent is inserted after a + * child then will update the child post-insert to set the relationship. If + * false then relationship will not be set. + */ + public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml, Boolean resolveOutOfOrderRelationships) { m_sObjectTypes = sObjectTypes.clone(); @@ -125,6 +158,10 @@ public virtual class fflib_SObjectUnitOfWork m_workList.add(m_emailWork); m_dml = dml; + + if (resolveOutOfOrderRelationships) { + attemptToResolveOutOfOrderRelationships(); + } } // default implementations for commitWork events @@ -144,7 +181,7 @@ public virtual class fflib_SObjectUnitOfWork * * @return this unit of work */ - public fflib_SObjectUnitOfWork attemptToResolveOutOfOrderRelationships() + private fflib_SObjectUnitOfWork attemptToResolveOutOfOrderRelationships() { m_attemptToResolveOutOfOrderRelationships = true; for (Relationships relationships : m_relationships.values()) { diff --git a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls index a01b6fb239b..dfaf036ba67 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls @@ -76,8 +76,7 @@ private with sharing class fflib_SObjectUnitOfWorkTest Account.SObjectType }; - fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder) - .attemptToResolveOutOfOrderRelationships(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder, true); List accounts = new List(); List contacts = new List(); for(Integer i=0; i<10; i++) From 232d81e2ed4d47e92a4bb04c8c6ba406d2b120aa Mon Sep 17 00:00:00 2001 From: John Rogers Date: Thu, 27 Dec 2018 15:56:28 -0800 Subject: [PATCH 3/8] enum for ways unit of work can handle unresolved relationships --- fflib/src/classes/fflib_SObjectUnitOfWork.cls | 66 +++++++++++-------- .../classes/fflib_SObjectUnitOfWorkTest.cls | 3 +- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectUnitOfWork.cls b/fflib/src/classes/fflib_SObjectUnitOfWork.cls index ba03ea68d54..566de345834 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWork.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWork.cls @@ -54,6 +54,14 @@ public virtual class fflib_SObjectUnitOfWork implements fflib_ISObjectUnitOfWork { + /* + * Ways a unit of work can handle unresolved relationships where child is inserted at same time or after the parent + * + * ATTEMPT_RESOLVE_OUT_OF_ORDER - Set the child lookup post-insert and update + * IGNORE_OUT_OF_ORDER - leave the lookup null (default behaviour) + */ + public enum UnresolvedRelationshipBehavior { ATTEMPT_RESOLVE_OUT_OF_ORDER, IGNORE_OUT_OF_ORDER } + protected List m_sObjectTypes = new List(); protected Map> m_newListByType = new Map>(); @@ -70,7 +78,8 @@ public virtual class fflib_SObjectUnitOfWork protected IDML m_dml; - protected Boolean m_attemptToResolveOutOfOrderRelationships = false; + protected UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour = + UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER; /** * Interface describes work to be performed during the commitWork method @@ -113,25 +122,25 @@ public virtual class fflib_SObjectUnitOfWork * Constructs a new UnitOfWork to support work against the given object list * * @param sObjectTypes A list of objects given in dependency order (least dependent first) - * @param resolveOutOfOrderRelationships If true and a relationship is registered where a parent is inserted after a - * child then will update the child post-insert to set the relationship. If - * false then relationship will not be set. + * @param unresolvedRelationshipsBehaviour If ATTEMPT_OUT_OF_ORDER_RELATIONSHIPS and a relationship is registered + * where a parent is inserted after a child then will update the child + * post-insert to set the relationship. If IGNORE_OUT_OF_ORDER then + * relationship will not be set. */ - public fflib_SObjectUnitOfWork(List sObjectTypes, Boolean resolveOutOfOrderRelationships) { - this(sObjectTypes, new SimpleDML(), resolveOutOfOrderRelationships); + public fflib_SObjectUnitOfWork(List sObjectTypes, + UnresolvedRelationshipBehavior unresolvedRelationshipBehavior) { + this(sObjectTypes, new SimpleDML(), unresolvedRelationshipBehavior); } /** * Constructs a new UnitOfWork to support work against the given object list * * @param sObjectTypes A list of objects given in dependency order (least dependent first) - * @param resolveOutOfOrderRelationships If true and a relationship is registered where a parent is inserted after a - * child then will update the child post-insert to set the relationship. If - * false then relationship will not be set. + * @param dml Modify the database via this class */ public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml) { - this(sObjectTypes, dml, false); + this(sObjectTypes, dml, UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER); } /** @@ -139,11 +148,13 @@ public virtual class fflib_SObjectUnitOfWork * * @param sObjectTypes A list of objects given in dependency order (least dependent first) * @param dml Modify the database via this class - * @param resolveOutOfOrderRelationships If true and a relationship is registered where a parent is inserted after a - * child then will update the child post-insert to set the relationship. If - * false then relationship will not be set. + * @param unresolvedRelationshipsBehaviour If ATTEMPT_OUT_OF_ORDER_RELATIONSHIPS and a relationship is registered + * where a parent is inserted after a child then will update the child + * post-insert to set the relationship. If IGNORE_OUT_OF_ORDER then + * relationship will not be set. */ - public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml, Boolean resolveOutOfOrderRelationships) + public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml, + UnresolvedRelationshipBehavior unresolvedRelationshipBehavior) { m_sObjectTypes = sObjectTypes.clone(); @@ -159,9 +170,7 @@ public virtual class fflib_SObjectUnitOfWork m_dml = dml; - if (resolveOutOfOrderRelationships) { - attemptToResolveOutOfOrderRelationships(); - } + setUnresolvedRelationshipsBehaviour(unresolvedRelationshipBehavior); } // default implementations for commitWork events @@ -179,13 +188,15 @@ public virtual class fflib_SObjectUnitOfWork * to set the relationship in a subsequent update. This is slightly less efficient than the default behaviour * of silently failing to set the lookup in this scenario. * + * @param behavior + * * @return this unit of work */ - private fflib_SObjectUnitOfWork attemptToResolveOutOfOrderRelationships() + private fflib_SObjectUnitOfWork setUnresolvedRelationshipsBehaviour(UnresolvedRelationshipBehavior behavior) { - m_attemptToResolveOutOfOrderRelationships = true; + m_unresolvedRelationshipBehaviour = behavior; for (Relationships relationships : m_relationships.values()) { - relationships.attemptToResolveOutOfOrderRelationships(true); + relationships.attemptToResolveOutOfOrderRelationships(m_unresolvedRelationshipBehaviour); } return this; } @@ -203,7 +214,7 @@ public virtual class fflib_SObjectUnitOfWork m_dirtyMapByType.put(sObjectType.getDescribe().getName(), new Map()); m_deletedMapByType.put(sObjectType.getDescribe().getName(), new Map()); m_relationships.put(sObjectType.getDescribe().getName(), - new Relationships().attemptToResolveOutOfOrderRelationships(m_attemptToResolveOutOfOrderRelationships)); + new Relationships().attemptToResolveOutOfOrderRelationships(m_unresolvedRelationshipBehaviour)); // give derived class opportunity to register the type onRegisterType(sObjectType); @@ -418,7 +429,7 @@ public virtual class fflib_SObjectUnitOfWork } // Resolve any unresolved relationships where parent was inserted after child, and so child lookup was not set - if (m_attemptToResolveOutOfOrderRelationships) + if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.ATTEMPT_RESOLVE_OUT_OF_ORDER) { for(Schema.SObjectType sObjectType : m_sObjectTypes) { @@ -476,7 +487,8 @@ public virtual class fflib_SObjectUnitOfWork private class Relationships { private List m_relationships = new List(); - private Boolean m_attemptToResolveOutOfOrderRelationships = false; + private UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour = + UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER; private List m_parentInsertedAfterChildRelationships = new List(); @@ -489,9 +501,9 @@ public virtual class fflib_SObjectUnitOfWork * * @return this object */ - public Relationships attemptToResolveOutOfOrderRelationships(Boolean attemptToResolve) + public Relationships attemptToResolveOutOfOrderRelationships(UnresolvedRelationshipBehavior behavior) { - m_attemptToResolveOutOfOrderRelationships = attemptToResolve; + m_unresolvedRelationshipBehaviour = behavior; return this; } @@ -504,7 +516,7 @@ public virtual class fflib_SObjectUnitOfWork relationship.resolve(); // Check if parent is inserted after the child - if (m_attemptToResolveOutOfOrderRelationships && + if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.ATTEMPT_RESOLVE_OUT_OF_ORDER && relationship instanceof RelationshipPermittingOutOfOrderInsert && !((RelationshipPermittingOutOfOrderInsert) relationship).Resolved) { @@ -565,7 +577,7 @@ public virtual class fflib_SObjectUnitOfWork public void add(SObject record, Schema.sObjectField relatedToField, SObject relatedTo) { // Relationship to resolve - if (!m_attemptToResolveOutOfOrderRelationships) + if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER) { Relationship relationship = new Relationship(); relationship.Record = record; diff --git a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls index dfaf036ba67..46e795e39da 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls @@ -76,7 +76,8 @@ private with sharing class fflib_SObjectUnitOfWorkTest Account.SObjectType }; - fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder, true); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder, + fflib_SObjectUnitOfWork.UnresolvedRelationshipBehavior.ATTEMPT_RESOLVE_OUT_OF_ORDER); List accounts = new List(); List contacts = new List(); for(Integer i=0; i<10; i++) From 41e11579bd6975429a60c96f789785f3e9a34328 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Sat, 16 Feb 2019 12:57:04 -0800 Subject: [PATCH 4/8] refactor: camel case relationship behaviour enum values --- fflib/src/classes/fflib_SObjectUnitOfWork.cls | 14 +++++++------- fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectUnitOfWork.cls b/fflib/src/classes/fflib_SObjectUnitOfWork.cls index 566de345834..35e2534787c 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWork.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWork.cls @@ -60,7 +60,7 @@ public virtual class fflib_SObjectUnitOfWork * ATTEMPT_RESOLVE_OUT_OF_ORDER - Set the child lookup post-insert and update * IGNORE_OUT_OF_ORDER - leave the lookup null (default behaviour) */ - public enum UnresolvedRelationshipBehavior { ATTEMPT_RESOLVE_OUT_OF_ORDER, IGNORE_OUT_OF_ORDER } + public enum UnresolvedRelationshipBehavior { AttemptResolveOutOfOrder, IgnoreOutOfOrder } protected List m_sObjectTypes = new List(); @@ -79,7 +79,7 @@ public virtual class fflib_SObjectUnitOfWork protected IDML m_dml; protected UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour = - UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER; + UnresolvedRelationshipBehavior.IgnoreOutOfOrder; /** * Interface describes work to be performed during the commitWork method @@ -140,7 +140,7 @@ public virtual class fflib_SObjectUnitOfWork */ public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml) { - this(sObjectTypes, dml, UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER); + this(sObjectTypes, dml, UnresolvedRelationshipBehavior.IgnoreOutOfOrder); } /** @@ -429,7 +429,7 @@ public virtual class fflib_SObjectUnitOfWork } // Resolve any unresolved relationships where parent was inserted after child, and so child lookup was not set - if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.ATTEMPT_RESOLVE_OUT_OF_ORDER) + if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.AttemptResolveOutOfOrder) { for(Schema.SObjectType sObjectType : m_sObjectTypes) { @@ -488,7 +488,7 @@ public virtual class fflib_SObjectUnitOfWork { private List m_relationships = new List(); private UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour = - UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER; + UnresolvedRelationshipBehavior.IgnoreOutOfOrder; private List m_parentInsertedAfterChildRelationships = new List(); @@ -516,7 +516,7 @@ public virtual class fflib_SObjectUnitOfWork relationship.resolve(); // Check if parent is inserted after the child - if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.ATTEMPT_RESOLVE_OUT_OF_ORDER && + if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.AttemptResolveOutOfOrder && relationship instanceof RelationshipPermittingOutOfOrderInsert && !((RelationshipPermittingOutOfOrderInsert) relationship).Resolved) { @@ -577,7 +577,7 @@ public virtual class fflib_SObjectUnitOfWork public void add(SObject record, Schema.sObjectField relatedToField, SObject relatedTo) { // Relationship to resolve - if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.IGNORE_OUT_OF_ORDER) + if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.IgnoreOutOfOrder) { Relationship relationship = new Relationship(); relationship.Record = record; diff --git a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls index 46e795e39da..8f96bebd3ea 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWorkTest.cls @@ -77,7 +77,7 @@ private with sharing class fflib_SObjectUnitOfWorkTest }; fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(dependencyOrder, - fflib_SObjectUnitOfWork.UnresolvedRelationshipBehavior.ATTEMPT_RESOLVE_OUT_OF_ORDER); + fflib_SObjectUnitOfWork.UnresolvedRelationshipBehavior.AttemptResolveOutOfOrder); List accounts = new List(); List contacts = new List(); for(Integer i=0; i<10; i++) From 3a494d7a92f31a73b518cd0ee2fb58fe292351aa Mon Sep 17 00:00:00 2001 From: John Rogers Date: Sun, 17 Feb 2019 11:38:57 -0800 Subject: [PATCH 5/8] feat: docs & unresolved relationship behaviour is final --- fflib/src/classes/fflib_SObjectUnitOfWork.cls | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectUnitOfWork.cls b/fflib/src/classes/fflib_SObjectUnitOfWork.cls index d9085ce4d6b..6bdfbd8e60b 100644 --- a/fflib/src/classes/fflib_SObjectUnitOfWork.cls +++ b/fflib/src/classes/fflib_SObjectUnitOfWork.cls @@ -54,14 +54,19 @@ public virtual class fflib_SObjectUnitOfWork implements fflib_ISObjectUnitOfWork { + /* - * Ways a unit of work can handle unresolved relationships where child is inserted at same time or after the parent + * Unit of work has two ways of resolving registered relationships that require an update to resolve (e.g. parent + * and child are same sobject type, or the parent is inserted after the child): * - * AttemptResolveOutOfOrder - Set the child lookup post-insert and update - * IgnoreOutOfOrder - leave the lookup null (default behaviour) + * AttemptResolveOutOfOrder - Update child to set the relationship (e.g. insert parent, insert child, update child) + * IgnoreOutOfOrder (default behaviour) - Do not set the relationship (e.g. leave lookup null) */ public enum UnresolvedRelationshipBehavior { AttemptResolveOutOfOrder, IgnoreOutOfOrder } + private static final UnresolvedRelationshipBehavior DEFAULT_UNRESOLVED_RELATIONSHIP_BEHAVIOR = + UnresolvedRelationshipBehavior.IgnoreOutOfOrder; + protected List m_sObjectTypes = new List(); protected Map> m_newListByType = new Map>(); @@ -83,8 +88,7 @@ public virtual class fflib_SObjectUnitOfWork protected IDML m_dml; - protected UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour = - UnresolvedRelationshipBehavior.IgnoreOutOfOrder; + protected final UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour; /** * Interface describes work to be performed during the commitWork method @@ -135,9 +139,9 @@ public virtual class fflib_SObjectUnitOfWork * Constructs a new UnitOfWork to support work against the given object list * * @param sObjectTypes A list of objects given in dependency order (least dependent first) - * @param unresolvedRelationshipsBehaviour If ATTEMPT_OUT_OF_ORDER_RELATIONSHIPS and a relationship is registered + * @param unresolvedRelationshipsBehaviour If AttemptOutOfOrderRelationships and a relationship is registered * where a parent is inserted after a child then will update the child - * post-insert to set the relationship. If IGNORE_OUT_OF_ORDER then + * post-insert to set the relationship. If IgnoreOutOfOrder then * relationship will not be set. */ public fflib_SObjectUnitOfWork(List sObjectTypes, @@ -153,7 +157,7 @@ public virtual class fflib_SObjectUnitOfWork */ public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml) { - this(sObjectTypes, dml, UnresolvedRelationshipBehavior.IgnoreOutOfOrder); + this(sObjectTypes, dml, DEFAULT_UNRESOLVED_RELATIONSHIP_BEHAVIOR); } /** @@ -161,14 +165,16 @@ public virtual class fflib_SObjectUnitOfWork * * @param sObjectTypes A list of objects given in dependency order (least dependent first) * @param dml Modify the database via this class - * @param unresolvedRelationshipsBehaviour If ATTEMPT_OUT_OF_ORDER_RELATIONSHIPS and a relationship is registered + * @param unresolvedRelationshipsBehaviour If AttemptOutOfOrderRelationships and a relationship is registered * where a parent is inserted after a child then will update the child - * post-insert to set the relationship. If IGNORE_OUT_OF_ORDER then - * relationship will not be set. + * post-insert to set the relationship. If IgnoreOutOfOrder then relationship + * will not be set. */ public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml, UnresolvedRelationshipBehavior unresolvedRelationshipBehavior) { + m_unresolvedRelationshipBehaviour = unresolvedRelationshipBehavior; + m_sObjectTypes = sObjectTypes.clone(); for (Schema.SObjectType sObjectType : m_sObjectTypes) @@ -177,11 +183,10 @@ public virtual class fflib_SObjectUnitOfWork handleRegisterType(sObjectType); } - m_relationships.put(Messaging.SingleEmailMessage.class.getName(), new Relationships()); + m_relationships.put(Messaging.SingleEmailMessage.class.getName(), + new Relationships(unresolvedRelationshipBehavior)); m_dml = dml; - - setUnresolvedRelationshipsBehaviour(unresolvedRelationshipBehavior); } // default implementations for commitWork events @@ -206,24 +211,6 @@ public virtual class fflib_SObjectUnitOfWork public virtual void onCommitWorkFinishing() {} public virtual void onCommitWorkFinished(Boolean wasSuccessful) {} - /** - * Calling this allows relationships class to track relationships that weren't resolved on insert and attempt - * to set the relationship in a subsequent update. This is slightly less efficient than the default behaviour - * of silently failing to set the lookup in this scenario. - * - * @param behavior - * - * @return this unit of work - */ - private fflib_SObjectUnitOfWork setUnresolvedRelationshipsBehaviour(UnresolvedRelationshipBehavior behavior) - { - m_unresolvedRelationshipBehaviour = behavior; - for (Relationships relationships : m_relationships.values()) { - relationships.attemptToResolveOutOfOrderRelationships(m_unresolvedRelationshipBehaviour); - } - return this; - } - /** * Registers the type to be used for DML operations * @@ -238,8 +225,7 @@ public virtual class fflib_SObjectUnitOfWork m_newListByType.put(sObjectName, new List()); m_dirtyMapByType.put(sObjectName, new Map()); m_deletedMapByType.put(sObjectName, new Map()); - m_relationships.put(sObjectName, - new Relationships().attemptToResolveOutOfOrderRelationships(m_unresolvedRelationshipBehaviour)); + m_relationships.put(sObjectName, new Relationships(m_unresolvedRelationshipBehaviour)); m_publishBeforeListByType.put(sObjectName, new List()); m_publishAfterSuccessListByType.put(sObjectName, new List()); @@ -748,24 +734,22 @@ public virtual class fflib_SObjectUnitOfWork private class Relationships { private List m_relationships = new List(); - private UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour = - UnresolvedRelationshipBehavior.IgnoreOutOfOrder; private List m_parentInsertedAfterChildRelationships = new List(); + private final UnresolvedRelationshipBehavior m_unresolvedRelationshipBehaviour; /** - * Calling this allows relationships class to track relationships that weren't resolved on insert and attempt - * to set the relationship in a subsequent update. This is slightly less efficient than the default behaviour - * of silently failing to set the lookup in this scenario. + * Unit of work has two ways of resolving registered relationships that require an update to resolve (e.g. + * parent and child are same sobject type, or the parent is inserted after the child): * - * @param attemptToResolve If true then will track relationships that weren't resolved on insert + * AttemptResolveOutOfOrder - Update child to set the relationship (e.g. insert parent, insert child, update + * child) + * IgnoreOutOfOrder (default behaviour) - Do not set the relationship (e.g. leave lookup null) * - * @return this object + * @param unresolvedRelationshipBehaviour The behaviour to use when encountering unresolved relationships */ - public Relationships attemptToResolveOutOfOrderRelationships(UnresolvedRelationshipBehavior behavior) - { - m_unresolvedRelationshipBehaviour = behavior; - return this; + public Relationships(UnresolvedRelationshipBehavior unresolvedRelationshipBehaviour) { + m_unresolvedRelationshipBehaviour = unresolvedRelationshipBehaviour; } public void resolve() @@ -778,7 +762,6 @@ public virtual class fflib_SObjectUnitOfWork // Check if parent is inserted after the child if (m_unresolvedRelationshipBehaviour == UnresolvedRelationshipBehavior.AttemptResolveOutOfOrder && - relationship instanceof RelationshipPermittingOutOfOrderInsert && !((RelationshipPermittingOutOfOrderInsert) relationship).Resolved) { m_parentInsertedAfterChildRelationships.add((RelationshipPermittingOutOfOrderInsert) relationship); @@ -882,6 +865,10 @@ public virtual class fflib_SObjectUnitOfWork } } + /** + * Similar to Relationship, but has a Resolved property that is set to false when relationship is not resolved + * because RelatedTo does not have an ID and/or resolve() has not been called. + */ private class RelationshipPermittingOutOfOrderInsert implements IRelationship { public SObject Record; public Schema.sObjectField RelatedToField; @@ -902,10 +889,10 @@ public virtual class fflib_SObjectUnitOfWork 'before child so child does not need an update. In unit of work initialization put ' + '' + RelatedTo.getSObjectType() + ' before ' + Record.getSObjectType()); } - resolved = false; + Resolved = false; } else { Record.put(RelatedToField, RelatedTo.Id); - resolved = true; + Resolved = true; } } } From 05c701e48bdb181da9d6645ce18ca09c905eb414 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Sun, 17 Feb 2019 11:41:53 -0800 Subject: [PATCH 6/8] chore: remove illuminated cloud files --- .idea/.name | 1 - .idea/encodings.xml | 4 - .idea/illuminatedCloud.xml | 14 - .idea/misc.xml | 6 - .idea/modules.xml | 8 - .idea/vcs.xml | 6 - .idea/workspace.xml | 1195 ------------------------------------ 7 files changed, 1234 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/illuminatedCloud.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 09fd886729c..00000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -fflib-apex-mocks \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 15a15b218a2..00000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/illuminatedCloud.xml b/.idea/illuminatedCloud.xml deleted file mode 100644 index d13c10fcc9a..00000000000 --- a/.idea/illuminatedCloud.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 1385bf45b06..00000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index d704b1cbecc..00000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4cb..00000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 6066ca9aaa0..00000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,1195 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enum - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -