It's hard to see what's going on from the diagram, which looks to be an older version of process builder.
It shouldn't make any difference whether it's passing in the 15 or 18 char ID.
In the new version of Process Builder, you should choose to populate the Volunteer Application field (as it looks like you're doing), then select the type as "Reference" then select the Id for the application object, which may be called "Volunteer Application ID" or simply "Record ID".
Make sure to activate the new version of the process flow as well!
I agree with @crop1645 that DLRS is a good option here, but if you must roll your own it would look something like:
Utilities
Getting Parent Ids
I consider the following utility essential. It is easy to write, easy to test, and is so ubiquitous it will add a lot of syntactic sugar over time. It also means you don't have to cache these collections. We use sets to improve compatibility with queries.
public class Pluck
{
public static Set<Id> ids(SObjectField field, List<SObject> record)
{
Set<Id> ids = new Set<Id>();
for (SObject record : records) ids.add((Id)record.get(field));
ids.remove(null);
return ids;
}
public static Set<String> strings(SObjectField field, List<SObject> records)
{
// etc.
}
}
Grouping
One of the first things you will need to do is be able to split the Note
records from your trigger out by parent record so you can use them to generate specific parent Notes__c
values. We also tend to have a utility around for this functionality. You can support String
keys in addition to SObjectField
by using some sort of model class like FieldReference and method overloads.
public class GroupBy
{
public static Map<Id, List<SObject>> ids(SObjectField field, List<SObject> records)
{
Map<Id, List<SObject>> byId = new Map<Id, List<SObject>>();
for (SObject record : records)
{
Id key = (Id)record.get(field);
if (!byId.containsKey(key)) byId.put(key, new List<SObject>());
byId.get(key).add(note);
}
return byId;
}
}
Service Class
Class Declaration
You will need a service class for Notes
. I usually pluralize Services
in the class name, because the way I see it you will do many different things in most service classes you write.
public with sharing class NoteServices
{
// methods
}
Consolidating Note Bodies
You need to compress these Note
records into one String
. It is very important to use List<String>
because you will want to preserve order, and de-duplication is not particularly helpful (or likely to make an impact).
public static String consolidateBodies(List<Note> notes)
{
if (notes == null) return '';
List<String> bodies = new List<String>();
for (Note note : notes) bodies.add(note.Body);
return String.join(bodies, '\n');
}
Getting Parent Notes
Once you know that you can get a List<Note>
for a specific parent, you can pass in these grouped records to the above method to map this String
by the parent's Id
.
public static Map<Id, String> consolidateBodies(List<Note> notes)
{
final SObjectField PARENT = Note.ParentId
Map<Id, List<Note>> parentToNotes = GroupBy.ids(PARENT, [
SELECT Body FROM Note
WHERE ParentId IN :Pluck.ids(PARENT, notes);
ORDER BY Id DESC // if you want newest first
]);
Map<Id, String> bodies = new Map<Id, String>();
for (Id parentId : parentIds)
bodies.put(parentId, consolidateBodies(parentToNotes.get(parentId)));
return bodies;
}
Using Note Bodies
You now have everything you need. Just putting it together, build up a list of parent records with the Notes__c
field populated. When you update
these records, make sure you handle DmlException
specifically. Typically from a trigger I would simply map the errors back to the source record (in this case a child record). I have an example of how to do that in a different answer.
public static List<SObject> syncParents(List<Note> notes)
{
Map<Id, String> bodies = consolidateBodies(notes);
List<SObject> parents = new List<SObject>();
for (Id parentId : parentIds)
{
SObject parent= parentId.getSObjectType().newSObject();
parent.put('Notes__c', bodies.get(parentId));
parent.Id = parentId;
parents.add(parent);
}
try { update parents; }
catch (DmlException dmx) { /*determine proper error handling*/ }
}
Best Answer
One thought comes to mind that might help make this more palatable for you and easier to manage, although you wouldn't be able to easily identify which object the original lookup came from. I'm going to call the lookup objects
Object_Ln
wheren
is a an integer that represents a number for the object to as many objects as you can create lookups to (BTW, for the purposes of SOQL, 10 is probably the practical limit since you can only traverse up to 10 relationships).You would create a junction object,
Object_J
that sits between what I'll callObject_C
and the many other objects you want to have the lookups to. The junction object would contain ALL of the fields for the various objects you are trying to manage the lookup to. The final link would be only from the junction object to your final custom object (Object_c
) which would have one field that looks up to the junction object record.In addition to Name, you'd probably want to have 1 special field on the junction object:
the recordId or "link" to that object record
The Name field should be populated with the name of the object the lookup is on and thus would not be unique
That kind of approach would seem to simplify determining what the actual look-up record/object is from your custom
Object_c
that is ultimately linked to the junctionObject_J
by only one link. If you're clever about it, you should be able to pass through the final link from the custom link field you created inObject_J
to a field on the destination custom object, thus requiring you to use at most two fields to accomplish what could have taken up to 20 fields as per the description in your question.The biggest difficulty you will likely encounter is "how" you'll create the link from the junction object to the object you want to have the lookup to. This might be a good use case for a flow to help simplify the record link-up process.