Skip to content

Commit 4c38538

Browse files
committed
Added new registerDirty method
This new method facilitates an update to a record where that update is for a lookup field
1 parent c1f53cd commit 4c38538

File tree

2 files changed

+82
-53
lines changed

2 files changed

+82
-53
lines changed

fflib/src/classes/fflib_ISObjectUnitOfWork.cls

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ public interface fflib_ISObjectUnitOfWork
5050
* @param record An existing record
5151
**/
5252
void registerDirty(SObject record);
53+
/**
54+
* Register an existing record to be updated when commitWork is called,
55+
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
56+
*
57+
* @param record A newly created SObject instance to be inserted during commitWork
58+
* @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent
59+
* @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separatly)
60+
**/
61+
void registerDirty(SObject record, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord);
5362
/**
5463
* Register a list of existing records to be updated during the commitWork method
5564
*

fflib/src/classes/fflib_SObjectUnitOfWork.cls

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22
* Copyright (c), FinancialForce.com, inc
33
* All rights reserved.
44
*
5-
* Redistribution and use in source and binary forms, with or without modification,
5+
* Redistribution and use in source and binary forms, with or without modification,
66
* are permitted provided that the following conditions are met:
77
*
8-
* - Redistributions of source code must retain the above copyright notice,
8+
* - Redistributions of source code must retain the above copyright notice,
99
* this list of conditions and the following disclaimer.
10-
* - Redistributions in binary form must reproduce the above copyright notice,
11-
* this list of conditions and the following disclaimer in the documentation
10+
* - Redistributions in binary form must reproduce the above copyright notice,
11+
* this list of conditions and the following disclaimer in the documentation
1212
* and/or other materials provided with the distribution.
13-
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
14-
* may be used to endorse or promote products derived from this software without
13+
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
14+
* may be used to endorse or promote products derived from this software without
1515
* specific prior written permission.
1616
*
17-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18-
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19-
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20-
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20+
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
2121
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2222
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
2323
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
@@ -28,24 +28,24 @@
2828
* Provides an implementation of the Enterprise Application Architecture Unit Of Work, as defined by Martin Fowler
2929
* http://martinfowler.com/eaaCatalog/unitOfWork.html
3030
*
31-
* "When you're pulling data in and out of a database, it's important to keep track of what you've changed; otherwise,
32-
* that data won't be written back into the database. Similarly you have to insert new objects you create and
31+
* "When you're pulling data in and out of a database, it's important to keep track of what you've changed; otherwise,
32+
* that data won't be written back into the database. Similarly you have to insert new objects you create and
3333
* remove any objects you delete."
3434
*
35-
* "You can change the database with each change to your object model, but this can lead to lots of very small database calls,
36-
* which ends up being very slow. Furthermore it requires you to have a transaction open for the whole interaction, which is
35+
* "You can change the database with each change to your object model, but this can lead to lots of very small database calls,
36+
* which ends up being very slow. Furthermore it requires you to have a transaction open for the whole interaction, which is
3737
* impractical if you have a business transaction that spans multiple requests. The situation is even worse if you need to
3838
* keep track of the objects you've read so you can avoid inconsistent reads."
3939
*
40-
* "A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done,
40+
* "A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done,
4141
* it figures out everything that needs to be done to alter the database as a result of your work."
4242
*
4343
* In an Apex context this pattern provides the following specific benifits
4444
* - Applies bulkfication to DML operations, insert, update and delete
4545
* - Manages a business transaction around the work and ensures a rollback occurs (even when exceptions are later handled by the caller)
46-
* - Honours dependency rules between records and updates dependent relationships automatically during the commit
46+
* - Honours dependency rules between records and updates dependent relationships automatically during the commit
4747
*
48-
* Please refer to the testMethod's in this class for example usage
48+
* Please refer to the testMethod's in this class for example usage
4949
*
5050
* TODO: Need to complete the 100% coverage by covering parameter exceptions in tests
5151
* TODO: Need to add some more test methods for more complex use cases and some unexpected (e.g. registerDirty and then registerDeleted)
@@ -55,25 +55,25 @@ public virtual class fflib_SObjectUnitOfWork
5555
implements fflib_ISObjectUnitOfWork
5656
{
5757
private List<Schema.SObjectType> m_sObjectTypes = new List<Schema.SObjectType>();
58-
58+
5959
private Map<String, List<SObject>> m_newListByType = new Map<String, List<SObject>>();
60-
60+
6161
private Map<String, Map<Id, SObject>> m_dirtyMapByType = new Map<String, Map<Id, SObject>>();
62-
62+
6363
private Map<String, Map<Id, SObject>> m_deletedMapByType = new Map<String, Map<Id, SObject>>();
64-
64+
6565
private Map<String, Relationships> m_relationships = new Map<String, Relationships>();
6666

6767
private List<IDoWork> m_workList = new List<IDoWork>();
6868

6969
private SendEmailWork m_emailWork = new SendEmailWork();
70-
70+
7171
private IDML m_dml;
72-
72+
7373
/**
7474
* Interface describes work to be performed during the commitWork method
7575
**/
76-
public interface IDoWork
76+
public interface IDoWork
7777
{
7878
void doWork();
7979
}
@@ -84,7 +84,7 @@ public virtual class fflib_SObjectUnitOfWork
8484
void dmlUpdate(List<SObject> objList);
8585
void dmlDelete(List<SObject> objList);
8686
}
87-
87+
8888
public class SimpleDML implements IDML
8989
{
9090
public void dmlInsert(List<SObject> objList){
@@ -111,17 +111,17 @@ public virtual class fflib_SObjectUnitOfWork
111111
public fflib_SObjectUnitOfWork(List<Schema.SObjectType> sObjectTypes, IDML dml)
112112
{
113113
m_sObjectTypes = sObjectTypes.clone();
114-
114+
115115
for(Schema.SObjectType sObjectType : m_sObjectTypes)
116116
{
117117
m_newListByType.put(sObjectType.getDescribe().getName(), new List<SObject>());
118118
m_dirtyMapByType.put(sObjectType.getDescribe().getName(), new Map<Id, SObject>());
119119
m_deletedMapByType.put(sObjectType.getDescribe().getName(), new Map<Id, SObject>());
120-
m_relationships.put(sObjectType.getDescribe().getName(), new Relationships());
120+
m_relationships.put(sObjectType.getDescribe().getName(), new Relationships());
121121
}
122122

123123
m_workList.add(m_emailWork);
124-
124+
125125
m_dml = dml;
126126
}
127127

@@ -140,7 +140,7 @@ public virtual class fflib_SObjectUnitOfWork
140140
{
141141
m_emailWork.registerEmail(email);
142142
}
143-
143+
144144
/**
145145
* Register a newly created SObject instance to be inserted when commitWork is called
146146
*
@@ -165,7 +165,7 @@ public virtual class fflib_SObjectUnitOfWork
165165
}
166166

167167
/**
168-
* Register a newly created SObject instance to be inserted when commitWork is called,
168+
* Register a newly created SObject instance to be inserted when commitWork is called,
169169
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
170170
*
171171
* @param record A newly created SObject instance to be inserted during commitWork
@@ -176,16 +176,16 @@ public virtual class fflib_SObjectUnitOfWork
176176
{
177177
if(record.Id != null)
178178
throw new UnitOfWorkException('Only new records can be registered as new');
179-
String sObjectType = record.getSObjectType().getDescribe().getName();
179+
String sObjectType = record.getSObjectType().getDescribe().getName();
180180
if(!m_newListByType.containsKey(sObjectType))
181181
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
182-
m_newListByType.get(sObjectType).add(record);
182+
m_newListByType.get(sObjectType).add(record);
183183
if(relatedToParentRecord!=null && relatedToParentField!=null)
184184
registerRelationship(record, relatedToParentField, relatedToParentRecord);
185185
}
186-
186+
187187
/**
188-
* Register a relationship between two records that have yet to be inserted to the database. This information will be
188+
* Register a relationship between two records that have yet to be inserted to the database. This information will be
189189
* used during the commitWork phase to make the references only when related records have been inserted to the database.
190190
*
191191
* @param record An existing or newly created record
@@ -194,12 +194,12 @@ public virtual class fflib_SObjectUnitOfWork
194194
*/
195195
public void registerRelationship(SObject record, Schema.sObjectField relatedToField, SObject relatedTo)
196196
{
197-
String sObjectType = record.getSObjectType().getDescribe().getName();
197+
String sObjectType = record.getSObjectType().getDescribe().getName();
198198
if(!m_newListByType.containsKey(sObjectType))
199199
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
200200
m_relationships.get(sObjectType).add(record, relatedToField, relatedTo);
201201
}
202-
202+
203203
/**
204204
* Register an existing record to be updated during the commitWork method
205205
*
@@ -209,12 +209,32 @@ public virtual class fflib_SObjectUnitOfWork
209209
{
210210
if(record.Id == null)
211211
throw new UnitOfWorkException('New records cannot be registered as dirty');
212-
String sObjectType = record.getSObjectType().getDescribe().getName();
212+
String sObjectType = record.getSObjectType().getDescribe().getName();
213213
if(!m_dirtyMapByType.containsKey(sObjectType))
214214
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
215-
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
215+
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
216216
}
217217

218+
/**
219+
* Register an existing record to be updated when commitWork is called,
220+
* you may also provide a reference to the parent record instance (should also be registered as new separatly)
221+
*
222+
* @param record A newly created SObject instance to be inserted during commitWork
223+
* @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent
224+
* @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separatly)
225+
**/
226+
public void registerDirty(SObject record, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord)
227+
{
228+
if(record.Id == null)
229+
throw new UnitOfWorkException('New records cannot be registered as dirty');
230+
String sObjectType = record.getSObjectType().getDescribe().getName();
231+
if(!m_dirtyMapByType.containsKey(sObjectType))
232+
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
233+
m_dirtyMapByType.get(sObjectType).put(record.Id, record);
234+
if(relatedToParentRecord!=null && relatedToParentField!=null)
235+
registerRelationship(record, relatedToParentField, relatedToParentRecord);
236+
}
237+
218238
/**
219239
* Register a list of existing records to be updated during the commitWork method
220240
*
@@ -237,12 +257,12 @@ public virtual class fflib_SObjectUnitOfWork
237257
{
238258
if(record.Id == null)
239259
throw new UnitOfWorkException('New records cannot be registered for deletion');
240-
String sObjectType = record.getSObjectType().getDescribe().getName();
260+
String sObjectType = record.getSObjectType().getDescribe().getName();
241261
if(!m_deletedMapByType.containsKey(sObjectType))
242262
throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType }));
243-
m_deletedMapByType.get(sObjectType).put(record.Id, record);
263+
m_deletedMapByType.get(sObjectType).put(record.Id, record);
244264
}
245-
265+
246266
/**
247267
* Register a list of existing records to be deleted during the commitWork method
248268
*
@@ -255,25 +275,25 @@ public virtual class fflib_SObjectUnitOfWork
255275
this.registerDeleted(record);
256276
}
257277
}
258-
278+
259279
/**
260280
* Takes all the work that has been registered with the UnitOfWork and commits it to the database
261281
**/
262282
public void commitWork()
263283
{
264-
// Wrap the work in its own transaction
265-
Savepoint sp = Database.setSavePoint();
284+
// Wrap the work in its own transaction
285+
Savepoint sp = Database.setSavePoint();
266286
try
267-
{
287+
{
268288
// Insert by type
269289
for(Schema.SObjectType sObjectType : m_sObjectTypes)
270290
{
271291
m_relationships.get(sObjectType.getDescribe().getName()).resolve();
272292
m_dml.dmlInsert(m_newListByType.get(sObjectType.getDescribe().getName()));
273-
}
293+
}
274294
// Update by type
275295
for(Schema.SObjectType sObjectType : m_sObjectTypes)
276-
m_dml.dmlUpdate(m_dirtyMapByType.get(sObjectType.getDescribe().getName()).values());
296+
m_dml.dmlUpdate(m_dirtyMapByType.get(sObjectType.getDescribe().getName()).values());
277297
// Delete by type (in reverse dependency order)
278298
Integer objectIdx = m_sObjectTypes.size() - 1;
279299
while(objectIdx>=0)
@@ -290,7 +310,7 @@ public virtual class fflib_SObjectUnitOfWork
290310
throw e;
291311
}
292312
}
293-
313+
294314
private class Relationships
295315
{
296316
private List<Relationship> m_relationships = new List<Relationship>();
@@ -301,7 +321,7 @@ public virtual class fflib_SObjectUnitOfWork
301321
for(Relationship relationship : m_relationships)
302322
relationship.Record.put(relationship.RelatedToField, relationship.RelatedTo.Id);
303323
}
304-
324+
305325
public void add(SObject record, Schema.sObjectField relatedToField, SObject relatedTo)
306326
{
307327
// Relationship to resolve
@@ -312,20 +332,20 @@ public virtual class fflib_SObjectUnitOfWork
312332
m_relationships.add(relationship);
313333
}
314334
}
315-
335+
316336
private class Relationship
317337
{
318338
public SObject Record;
319339
public Schema.sObjectField RelatedToField;
320340
public SObject RelatedTo;
321341
}
322-
342+
323343
/**
324344
* UnitOfWork Exception
325345
**/
326346
public class UnitOfWorkException extends Exception {}
327347

328-
/**
348+
/**
329349
* Internal implementation of Messaging.sendEmail, see outer class registerEmail method
330350
**/
331351
private class SendEmailWork implements IDoWork
@@ -346,5 +366,5 @@ public virtual class fflib_SObjectUnitOfWork
346366
{
347367
if(emails.size() > 0) Messaging.sendEmail(emails);
348368
}
349-
}
369+
}
350370
}

0 commit comments

Comments
 (0)