9

I'm doing some polymorphic programming, and I decided that I wanted to be able to gracefully handle partial saves using SaveResult without blocking the user experience. The code looks roughly like this:

static void logDmlErrors(SObject[] src, SObject[] dest, SaveResult[] results, String message) {
    // Transform results into Chatter error messages, send them to admins
}

I then went about writing the utility methods that would use this function:

public static void insertRecords(SObject[] src, SObject[] dest, String message) {
    logDmlErrors(src, dest, Database.insert(dest, false), message);
}

Everything went well, until I came across the delete and undelete methods, which instead use DeleteResult and UndeleteResult. Both of those two classes contain the exact same methods as SaveResult, and yet don't appear to inherit from a common ancestor. This means that I can't just cast to a super-type to get to this information.

Thankfully, Summer 16 apparently introduced support for serializing (and deserializing) these classes as JSON, so I do have a fairly expensive (but accurate) workaround if I want proper polymorphic behavior, but I thought I'd throw it out there and see if someone has an alternative solution to this problem.

3
  • On the logDmlErrors method signature, use Object[] results instead of SaveResult[] results? Not offering type safety but best of the bad choices? Then instanceof for the 3 result types and 3 copies of the same lines of code - but I guess that is the code duplication you are trying to avoid. (No common ancestor exposed that I can see.)
    – Keith C
    Jun 6, 2016 at 20:17
  • is creating an interface and an adapter class to act as a pseudo-super-type not an option? 3 constructors to handle your 3 Result* classes, 3 attributes to hold them, plus a class attribute to determine which constructor was called (to determine which Result* attribute to use) should make implementing their hypothetical interface a simple matter.
    – Derek F
    Jun 6, 2016 at 20:52
  • good to know about the json support
    – cropredy
    Jun 6, 2016 at 21:28

1 Answer 1

13

Don't forget Database.UpsertResult!

In my DML library, I wrote a GenericResult class and used a factory pattern. For maximum reusability, you could certainly incorporate constructor overloads.

public class GenericResult
{
    final Id recordId;
    final List<Database.Error> errors;
    public Id getId() { return recordId; }
    public List<Database.Error> getErrors() { return errors; }

    public GenericResult(Id recordId, List<Database.Error> errors)
    {
        this.recordId = recordId;
        this.errors = (errors != null) ? errors : new List<Database.Error>();
    }
    public GenericResult(Database.SaveResult result)
    {
        this(result.getId(), this.getErrors());
    }
    public GenericResult(Database.UpsertResult result)
    {
        this(result.getId(), this.getErrors());
    }
    public GenericResult(Database.DeleteResult result)
    {
        this(result.getId(), this.getErrors());
    }
    public GenericResult(Database.UndeleteResult result)
    {
        this(result.getId(), this.getErrors());
    }
}
4
  • this is better than the way I did it with interfaces. Much cleaner -- one useful helper method would be to return the list of database errors as a string, delimited by \n.
    – cropredy
    Jun 6, 2016 at 21:32
  • Totally. I minimized the amount of logic in my result class, but do concatenate the messages elsewhere (with a ; delimiter instead).
    – Adrian Larson
    Jun 6, 2016 at 21:45
  • Thanks, Adrian. I'm going with a slightly modified version of this answer. I'm disappointed that we can't be more polymorphic about this.
    – sfdcfox
    Jun 8, 2016 at 4:16
  • Me too. I played around with it but eventually went with the simplest solution I could find.
    – Adrian Larson
    Jun 8, 2016 at 14:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .