From 075bb9f9938e2e758fc194334c686a6430810920 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Tue, 7 Apr 2020 14:52:41 +0200 Subject: [PATCH 01/10] :star: Add support for upsert by external Id --- .../main/classes/fflib_SObjectUnitOfWork.cls | 103 ++++++++++++++++-- 1 file changed, 91 insertions(+), 12 deletions(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index c8b38743cce..4ae5405f77b 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -60,6 +60,9 @@ public virtual class fflib_SObjectUnitOfWork protected Map> m_dirtyMapByType = new Map>(); + protected Map> m_upsertRecordsPerType = new Map>(); + protected Map m_externalIdToUpsertPerType = new Map(); + protected Map> m_deletedMapByType = new Map>(); protected Map> m_emptyRecycleBinMapByType = new Map>(); @@ -88,6 +91,7 @@ public virtual class fflib_SObjectUnitOfWork { void dmlInsert(List objList); void dmlUpdate(List objList); + void dmlUpsert(List objList, Schema.SObjectField externalId); void dmlDelete(List objList); void eventPublish(List objList); void emptyRecycleBin(List objList); @@ -103,7 +107,12 @@ public virtual class fflib_SObjectUnitOfWork { update objList; } - public virtual void dmlDelete(List objList) + public void dmlUpsert(List objList, Schema.SObjectField externalId) { + if (!objList.isEmpty()) { + Database.upsert(objList, externalId); + } + } + public void dmlDelete(List objList) { delete objList; } @@ -181,6 +190,7 @@ public virtual class fflib_SObjectUnitOfWork // add type to dml operation tracking m_newListByType.put(sObjectName, new List()); m_dirtyMapByType.put(sObjectName, new Map()); + m_upsertRecordsPerType.put(sObjectName, new List()); m_deletedMapByType.put(sObjectName, new Map()); m_emptyRecycleBinMapByType.put(sObjectName, new Map()); m_relationships.put(sObjectName, new Relationships()); @@ -456,6 +466,59 @@ public virtual class fflib_SObjectUnitOfWork } } + /** + * Register a list of mix of new and existing records to be inserted updated during the commitWork method by and external id + * + * @param records A list of mix of new and existing records + * @param externalIdField External id field to use for upserting operation + **/ + public void registerUpsert(List records, Schema.SObjectField externalIdField) + { + for (SObject record : records) + { + this.registerUpsert(record, externalIdField, null, null); + } + } + + + /** + * Register an existing or new record to be upserted when commitWork is called using an external id field, + * you may also provide a reference to the parent record instance (should also be registered as new separately) + * + * @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 separately) + **/ + public void registerUpsert(SObject record, Schema.SObjectField externalIdField, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord) + { + + SObjectType sObjectType = record.getSObjectType(); + String sObjName = sObjectType.getDescribe().getName(); + + assertForNonEventSObjectType(sObjName); + assertForSupportedSObjectType(m_upsertRecordsPerType, sObjName); + + if (externalIdField == null) + throw new UnitOfWorkException('Invalid argument: externalIdField. If you want to upsert by id, use the registerUpsert method that has only one argument'); + + assertValidExternalId(sObjectType, externalIdField); + + Schema.SObjectField registeredExternalId = m_externalIdToUpsertPerType.get(sObjName); + if (registeredExternalId != null && registeredExternalId != externalIdField) + { + throw new UnitOfWorkException(String.format( + 'SObject type {0} has already registered an upsert by external id {1}, you cannot use another is this unit of work.', + new List {sObjName, registeredExternalId.getDescribe().getName()} + )); + } + + m_upsertRecordsPerType.get(sObjName).add(record); + m_externalIdToUpsertPerType.put(sObjName, externalIdField); + + if (relatedToParentRecord!=null && relatedToParentField!=null) + registerRelationship(record, relatedToParentField, relatedToParentRecord); + } + /** * Register an existing record to be deleted during the commitWork method * @@ -624,6 +687,7 @@ public virtual class fflib_SObjectUnitOfWork onDMLStarting(); insertDmlByType(); updateDmlByType(); + upsertDmlByType(); deleteDmlByType(); emptyRecycleBinByType(); resolveEmailRelationships(); @@ -687,6 +751,15 @@ public virtual class fflib_SObjectUnitOfWork } } + private void upsertDmlByType() + { + String sobjName; + for (Schema.SObjectType sObjectType : m_sObjectTypes) { + sobjName = sObjectType.getDescribe().getName(); + m_dml.dmlUpsert(m_upsertRecordsPerType.get(sobjName), m_externalIdToUpsertPerType.get(sobjName)); + } + } + private void deleteDmlByType() { Integer objectIdx = m_sObjectTypes.size() - 1; @@ -775,6 +848,22 @@ public virtual class fflib_SObjectUnitOfWork } } + private static void assertValidExternalId(Schema.SObjectType relatedObject, Schema.SObjectField externalIdField) + { + + String externalIdFieldName = externalIdField.getDescribe().getName(); + Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); + Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); + + if (!relatedHasExternalIdField) { + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + } + + if (!externalIdFieldIsValid) { + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); + } + } + private class Relationships { private List m_relationships = new List(); @@ -804,17 +893,7 @@ public virtual class fflib_SObjectUnitOfWork List relatedObjects = relatedToField.getDescribe().getReferenceTo(); Schema.SObjectType relatedObject = relatedObjects[0]; - String externalIdFieldName = externalIdField.getDescribe().getName(); - Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); - Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); - - if (!relatedHasExternalIdField) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); - } - - if (!externalIdFieldIsValid) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); - } + assertValidExternalId(relatedObject, externalIdField); RelationshipByExternalId relationship = new RelationshipByExternalId(); relationship.Record = record; From 7ccdc3bbc9d09fb9404ba199ed092243d08e02cb Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Wed, 8 Apr 2020 11:55:28 +0200 Subject: [PATCH 02/10] :golf: Add upsert by external id test --- .../main/classes/fflib_SObjectUnitOfWork.cls | 50 +++++++++++-------- .../classes/fflib_SObjectUnitOfWorkTest.cls | 21 ++++++++ 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index 4ae5405f77b..ee3f9ce4611 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -109,7 +109,12 @@ public virtual class fflib_SObjectUnitOfWork } public void dmlUpsert(List objList, Schema.SObjectField externalId) { if (!objList.isEmpty()) { - Database.upsert(objList, externalId); + + Type objListType = Type.ForName('List<' + objList[0].getSObjectType() + '>'); + List typpedList = (List)objListType.newInstance(); + typpedList.addAll(objList); + + Database.upsert(typpedList, externalId); } } public void dmlDelete(List objList) @@ -491,7 +496,7 @@ public virtual class fflib_SObjectUnitOfWork **/ public void registerUpsert(SObject record, Schema.SObjectField externalIdField, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord) { - + SObjectType sObjectType = record.getSObjectType(); String sObjName = sObjectType.getDescribe().getName(); @@ -501,7 +506,18 @@ public virtual class fflib_SObjectUnitOfWork if (externalIdField == null) throw new UnitOfWorkException('Invalid argument: externalIdField. If you want to upsert by id, use the registerUpsert method that has only one argument'); - assertValidExternalId(sObjectType, externalIdField); + String externalIdFieldName = externalIdField.getDescribe().getName(); + Boolean relatedHasExternalIdField = sObjectType.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); + Boolean externalIdFieldIsValid = externalIdField.getDescribe().isIdLookup(); + + if (record.Id != null && externalIdFieldName != 'Id') + throw new UnitOfWorkException('When upserting by external id, the record cannot already have the standard id populated'); + + if (!relatedHasExternalIdField) + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + + if (!externalIdFieldIsValid) + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied cannot be used with upsert.'); Schema.SObjectField registeredExternalId = m_externalIdToUpsertPerType.get(sObjName); if (registeredExternalId != null && registeredExternalId != externalIdField) @@ -848,22 +864,6 @@ public virtual class fflib_SObjectUnitOfWork } } - private static void assertValidExternalId(Schema.SObjectType relatedObject, Schema.SObjectField externalIdField) - { - - String externalIdFieldName = externalIdField.getDescribe().getName(); - Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); - Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); - - if (!relatedHasExternalIdField) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); - } - - if (!externalIdFieldIsValid) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); - } - } - private class Relationships { private List m_relationships = new List(); @@ -893,7 +893,17 @@ public virtual class fflib_SObjectUnitOfWork List relatedObjects = relatedToField.getDescribe().getReferenceTo(); Schema.SObjectType relatedObject = relatedObjects[0]; - assertValidExternalId(relatedObject, externalIdField); + String externalIdFieldName = externalIdField.getDescribe().getName(); + Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); + Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); + + if (!relatedHasExternalIdField) { + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + } + + if (!externalIdFieldIsValid) { + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); + } RelationshipByExternalId relationship = new RelationshipByExternalId(); relationship.Record = record; diff --git a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls index 6d4019fc661..0f1a1f7928c 100644 --- a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls @@ -568,6 +568,27 @@ private with sharing class fflib_SObjectUnitOfWorkTest System.assertEquals(2, [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed']); } + @isTest + private static void testRegisterUpsertByExternalId() { + + Opportunity existingOpp = new Opportunity(Name = 'Existing Opportunity', StageName = 'Open', CloseDate = System.today()); + insert existingOpp; + + existingOpp.StageName = 'Closed'; + + System.assertEquals(1, [SELECT COUNT() FROM Opportunity]); + System.assertEquals(0, [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed']); + + Test.startTest(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(MY_SOBJECTS); + uow.registerUpsert(new List{existingOpp}, Opportunity.Id); + uow.commitWork(); + Test.stopTest(); + + System.assertEquals(1, [SELECT COUNT() FROM Opportunity]); + System.assertEquals(1, [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed']); + } + /** * Assert that actual events exactly match expected events (size, order and name) * and types match expected types From 11d9de20e2b3e5f6c654e371e59572c9ea517162 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Thu, 16 Apr 2020 00:04:08 +0200 Subject: [PATCH 03/10] :scissors: Resolve relationships on upsert --- .../main/classes/fflib_SObjectUnitOfWork.cls | 4 +- .../classes/fflib_SObjectUnitOfWorkTest.cls | 91 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index ee3f9ce4611..c3a0abc3550 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -772,6 +772,7 @@ public virtual class fflib_SObjectUnitOfWork String sobjName; for (Schema.SObjectType sObjectType : m_sObjectTypes) { sobjName = sObjectType.getDescribe().getName(); + m_relationships.get(sobjName).resolve(); m_dml.dmlUpsert(m_upsertRecordsPerType.get(sobjName), m_externalIdToUpsertPerType.get(sobjName)); } } @@ -964,7 +965,8 @@ public virtual class fflib_SObjectUnitOfWork public void resolve() { - this.Record.put( this.RelatedToField, this.RelatedTo.Id); + if (this.Record.get(this.RelatedToField) == null) + this.Record.put( this.RelatedToField, this.RelatedTo.Id); } } diff --git a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls index 0f1a1f7928c..03afbfb81dc 100644 --- a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls @@ -589,6 +589,97 @@ private with sharing class fflib_SObjectUnitOfWorkTest System.assertEquals(1, [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed']); } + + @isTest + private static void testRegisterUpsertByExternalIdParentWillResolve() { + + Opportunity existingOpp = new Opportunity(Name = 'Existing Opportunity', StageName = 'Open', CloseDate = System.today()); + insert existingOpp; + + existingOpp.StageName = 'Closed'; + + System.assertEquals(1, [SELECT COUNT() FROM Opportunity]); + System.assertEquals(0, [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed']); + + List orderWithAccounts = new List(); + orderWithAccounts.add(Account.SObjectType); + orderWithAccounts.addAll(MY_SOBJECTS); + + String accName = 'new account'; + Account newAccount = new Account(Name = accName); + + Test.startTest(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(orderWithAccounts); + uow.registerNew(newAccount); + uow.registerUpsert(existingOpp, Opportunity.Id, Opportunity.AccountId, newAccount); + uow.commitWork(); + Test.stopTest(); + + System.assertEquals(1, [SELECT COUNT() FROM Opportunity]); + + Opportunity updatedOpp = [SELECT StageName, Account.Name FROM Opportunity]; + System.assertEquals('Closed', updatedOpp.StageName); + System.assertEquals(accName, updatedOpp.Account.Name ); + } + + + @isTest + private static void testRegisterUpsertByExternalIdChildWillResolve() { + + Opportunity newOpportunity = new Opportunity(Name = 'Existing Opportunity', StageName = 'Closed', CloseDate = System.today()); + + Account existingAccount = new Account(Name = 'old account name'); + insert existingAccount; + + List orderWithAccounts = new List(); + orderWithAccounts.add(Account.SObjectType); + orderWithAccounts.addAll(MY_SOBJECTS); + + String accName = 'new account'; + + existingAccount.Name = accName; + + Test.startTest(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(orderWithAccounts); + uow.registerDirty(existingAccount); + uow.registerUpsert(newOpportunity, Opportunity.Id, Opportunity.AccountId, existingAccount); + uow.commitWork(); + Test.stopTest(); + + System.assertEquals(1, [SELECT COUNT() FROM Opportunity]); + + Opportunity updatedOpp = [SELECT StageName, Account.Name FROM Opportunity]; + System.assertEquals('Closed', updatedOpp.StageName); + System.assertEquals(accName, updatedOpp.Account.Name ); + } + + + @isTest + private static void testRegisterUpsertByExternalIdBothWillResolve() { + + Opportunity newOpportunity = new Opportunity(Name = 'Existing Opportunity', StageName = 'Closed', CloseDate = System.today()); + + String accName = 'new account'; + Account newAccount = new Account(Name = accName); + + List orderWithAccounts = new List(); + orderWithAccounts.add(Account.SObjectType); + orderWithAccounts.addAll(MY_SOBJECTS); + + Test.startTest(); + fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(orderWithAccounts); + uow.registerNew(newAccount); + uow.registerUpsert(newOpportunity, Opportunity.Id, Opportunity.AccountId, newAccount); + uow.commitWork(); + Test.stopTest(); + + System.assertEquals(1, [SELECT COUNT() FROM Opportunity]); + + Opportunity updatedOpp = [SELECT StageName, Account.Name FROM Opportunity]; + System.assertEquals('Closed', updatedOpp.StageName); + System.assertEquals(accName, updatedOpp.Account.Name ); + } + /** * Assert that actual events exactly match expected events (size, order and name) * and types match expected types From 961869f3d7fda26a0352fb09578d9479a1ea9ae4 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Thu, 16 Apr 2020 00:04:57 +0200 Subject: [PATCH 04/10] :scissors: Move upsert operation between insert and update to be more consistent --- .../apex-common/main/classes/fflib_SObjectUnitOfWork.cls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index c3a0abc3550..c48dd63eb89 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -701,9 +701,9 @@ public virtual class fflib_SObjectUnitOfWork onPublishBeforeEventsFinished(); onDMLStarting(); - insertDmlByType(); - updateDmlByType(); + insertDmlByType(); upsertDmlByType(); + updateDmlByType(); deleteDmlByType(); emptyRecycleBinByType(); resolveEmailRelationships(); From f5a4ca8ed0a95d3cfb9ee1614ac8863e2b1ba487 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Fri, 8 Jan 2021 09:03:07 +0100 Subject: [PATCH 05/10] :back: Revert resolve on empty --- .../apex-common/main/classes/fflib_SObjectUnitOfWork.cls | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index c48dd63eb89..b0d42b1285e 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -965,8 +965,7 @@ public virtual class fflib_SObjectUnitOfWork public void resolve() { - if (this.Record.get(this.RelatedToField) == null) - this.Record.put( this.RelatedToField, this.RelatedTo.Id); + this.Record.put( this.RelatedToField, this.RelatedTo.Id); } } From b2207d9684e8b4108e3d844c439d105f1173e413 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Fri, 8 Jan 2021 12:31:45 +0100 Subject: [PATCH 06/10] :bug: Control multiple resolving To mitigate creatable but not upateable fields --- .../main/classes/fflib_SObjectUnitOfWork.cls | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index b0d42b1285e..0e49e783076 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -874,8 +874,10 @@ public virtual class fflib_SObjectUnitOfWork // Resolve relationships for (IRelationship relationship : m_relationships) { - //relationship.Record.put(relationship.RelatedToField, relationship.RelatedTo.Id); - relationship.resolve(); + if (!relationship.isResolved()) + { + relationship.resolve(); + } } } @@ -938,6 +940,7 @@ public virtual class fflib_SObjectUnitOfWork private interface IRelationship { void resolve(); + Boolean isResolved(); } private class RelationshipByExternalId implements IRelationship @@ -955,6 +958,12 @@ public virtual class fflib_SObjectUnitOfWork relationshipObject.put( ExternalIdField.getDescribe().getName(), this.ExternalId ); this.Record.putSObject( this.RelationshipName, relationshipObject ); } + + + public Boolean isResolved() + { + return this.Record.getSObject( this.RelationshipName ) != null; + } } private class Relationship implements IRelationship @@ -964,8 +973,16 @@ public virtual class fflib_SObjectUnitOfWork public SObject RelatedTo; public void resolve() + { + if (String.isNotBlank(this.RelatedTo.Id)) + { + this.Record.put( this.RelatedToField, this.RelatedTo.Id ); + } + } + + public Boolean isResolved() { - this.Record.put( this.RelatedToField, this.RelatedTo.Id); + return String.isNotBlank( this.RelatedTo.Id ) && this.Record.get( this.RelatedToField ) == this.RelatedTo.Id; } } @@ -978,6 +995,11 @@ public virtual class fflib_SObjectUnitOfWork { this.email.setWhatId( this.relatedTo.Id ); } + + public Boolean isResolved() + { + return String.isNotBlank( this.RelatedTo.Id ) && this.email.getWhatId() == this.RelatedTo.Id; + } } /** From dee3bacc3cf2f7ea57eaec656f80b587dc53004d Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Fri, 8 Jan 2021 12:32:42 +0100 Subject: [PATCH 07/10] :golf: Add upsert to tests that use the UoW IDML --- .../apex-common/test/classes/fflib_ApplicationTest.cls | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sfdx-source/apex-common/test/classes/fflib_ApplicationTest.cls b/sfdx-source/apex-common/test/classes/fflib_ApplicationTest.cls index 66c104a585a..2be666234ac 100644 --- a/sfdx-source/apex-common/test/classes/fflib_ApplicationTest.cls +++ b/sfdx-source/apex-common/test/classes/fflib_ApplicationTest.cls @@ -442,6 +442,7 @@ private class fflib_ApplicationTest { public boolean isInsertCalled = false; public boolean isUpdateCalled = false; + public boolean isUpsertCalled = false; public boolean isDeleteCalled = false; public boolean isPublishCalled = false; public Boolean isEmptyRecycleBinCalled = false; @@ -452,6 +453,9 @@ private class fflib_ApplicationTest public void dmlUpdate(List objList){ this.isUpdateCalled = true; } + public void dmlUpsert(List objList, Schema.SObjectField externalId){ + this.isUpsertCalled = true; + } public void dmlDelete(List objList){ this.isDeleteCalled = true; } From 1c4121e7385fab01ed9828d6fed9cf38dfaafe4d Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Fri, 8 Jan 2021 14:40:09 +0100 Subject: [PATCH 08/10] :bug: Missing virtualization of dml methods --- .../apex-common/main/classes/fflib_SObjectUnitOfWork.cls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index 0e49e783076..ebea5e0dd42 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -107,7 +107,7 @@ public virtual class fflib_SObjectUnitOfWork { update objList; } - public void dmlUpsert(List objList, Schema.SObjectField externalId) { + public virtual void dmlUpsert(List objList, Schema.SObjectField externalId) { if (!objList.isEmpty()) { Type objListType = Type.ForName('List<' + objList[0].getSObjectType() + '>'); @@ -117,7 +117,7 @@ public virtual class fflib_SObjectUnitOfWork Database.upsert(typpedList, externalId); } } - public void dmlDelete(List objList) + public virtual void dmlDelete(List objList) { delete objList; } From c12f64a0394be0457d5f5c84d7adb5f8b9a88673 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Sat, 21 Jan 2023 12:01:19 +0100 Subject: [PATCH 09/10] Fix compilation issue and test --- .../test/classes/fflib_SObjectUnitOfWorkTest.cls | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls index 6ad0262257e..6cda1f4e9b8 100644 --- a/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls +++ b/sfdx-source/apex-common/test/classes/fflib_SObjectUnitOfWorkTest.cls @@ -740,7 +740,7 @@ private with sharing class fflib_SObjectUnitOfWorkTest System.assertEquals('Closed', updatedOpp.StageName); System.assertEquals(accName, updatedOpp.Account.Name ); } - + @IsTest private static void constructUserModeDML_When_DefaultConstructor_Expect_UserMode(){ fflib_SObjectUnitOfWork.UserModeDML dml = new fflib_SObjectUnitOfWork.UserModeDML(); @@ -931,6 +931,7 @@ private with sharing class fflib_SObjectUnitOfWorkTest public List recordsForInsert = new List(); public List recordsForUpdate = new List(); public List recordsForDelete = new List(); + public List recordsForUpsert = new List(); public List recordsForRecycleBin = new List(); public List recordsForEventPublish = new List(); @@ -949,6 +950,11 @@ private with sharing class fflib_SObjectUnitOfWorkTest this.recordsForDelete.addAll(objList); } + public void dmlUpsert(List objList, Schema.SObjectField externalId) + { + this.recordsForUpsert.addAll(objList); + } + public void eventPublish(List objList) { this.recordsForEventPublish.addAll(objList); From eb74f9ae3a85b9282517ddbbd3f09ba7c51cb9b2 Mon Sep 17 00:00:00 2001 From: Jonathan Wiesel Date: Sat, 21 Jan 2023 12:04:12 +0100 Subject: [PATCH 10/10] Ignoring case when comparing with id --- .../apex-common/main/classes/fflib_SObjectUnitOfWork.cls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index d454899a1e3..e294da3b4f6 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -529,7 +529,7 @@ public virtual class fflib_SObjectUnitOfWork Boolean relatedHasExternalIdField = sObjectType.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); Boolean externalIdFieldIsValid = externalIdField.getDescribe().isIdLookup(); - if (record.Id != null && externalIdFieldName != 'Id') + if (record.Id != null && !externalIdFieldName.equalsIgnoreCase('id')) throw new UnitOfWorkException('When upserting by external id, the record cannot already have the standard id populated'); if (!relatedHasExternalIdField)