apex-enterprise-patterns / fflib-apex-common

Common Apex Library supporting Apex Enterprise Patterns and much more!
BSD 3-Clause "New" or "Revised" License
913 stars 518 forks source link

Add methods and unit test coverage to fflib_SObjects domain #330

Closed wimvelzeboer closed 2 years ago

wimvelzeboer commented 3 years ago

Thanks for merging the new domain structure!

As mentioned earlier I have a few extra methods for the domain class, a few more test for beter code coverage and some added apex-docs comments


This change is Reviewable

wimvelzeboer commented 3 years ago

@ImJohnMDaniel @daveespo Could you have a look at this PR? This will complete the domain class methods. I wanted to add them earlier into the new domain structure change, but didn't want to keep on adding changes to that already large PR.

JAertgeerts commented 3 years ago

@ImJohnMDaniel @daveespo can this get reviewed and merged please?

wimvelzeboer commented 2 years ago

Is this change intentional? It appears to be returning objects with NOT blank fields

@daveespo That is indeed not correct. Its now fixed!

wimvelzeboer commented 2 years ago

Second, it's poor practice to name a variable the same as a Type -- there are too many places you can end up with weird compiler errors because it can't distinguish between the Type and the instance variable. Please do what you did in the method above and name the SObjectField argument variable field for this method and all of the ones below it (see pmd/pmd#2322 (comment) if you need some color on why this is poor practice)

Yeah, that it true when method become long. I typically have short methods and then these issues typically do not occur. But I have changed all sObjectField variable names into field.

Additionally, these 3 new convenience methods are not DRY .. I think a DRYer pattern would be something like

I have been puzzling with this for a while, but Apex is a bit limited in casting a Set<Id> to Set<Object>, and visa-versa. Your example with the populateValueSet looks very nice, and I would love to implement it, but it does not compile.

protected Set<Id> getIdFieldValues(Schema.SObjectField field)   {
    Set<Id> result = new Set<Id>();
    populateValueSet(field, result);
    return result;
}
protected void populateValueSet(Schema.SObjectField field, Set<Object> valueSet) {
    for (SObject record : getRecords())
    {
        valueSet.add(record.get(field));
    }
}

This throws the compile error: Method does not exist or incorrect signature: void populateValueSet(Schema.SObjectField, Set<Id>) from the type fflib_SObjects (167:3) I have tried other variants where I used a similar method like populateValueSet. got a Set returned, then iterated over that set to cast every single item to the desired type. Maybe I missing something but I have not been able to cast Set<Object> to Set<Id>. That's is why I ended up with this, not to nice indeed, NON-DRY solution. But at least it is faster and it still does avoid tons more of iterations in project/application/domain code. @daveespo Any other thoughts on this on how to solve it?

Shouldn't this be a straight up .equals() not a .containsAll()?

That makes more sense indeed! Its modified!

wimvelzeboer commented 2 years ago

@daveespo And another thought, the whole reason to add these methods like getIdFieldValues, is that we are unable to cast Set<Object> to Set<Id>. If we could have done that, then we could simply write a domain method like the following:

public Set<Id> getAccountIds()
{
    return (Set<Id>) getFieldValues(Schema.Contact.AccountId);
}

then we wouldn't need all those extra getXXXFieldValues() methods.

wimvelzeboer commented 2 years ago

What is the reasoning behind this method? Apex does not provide a "getFirst...()" method for the List class.

I can recall a discussion at my workplace about bulkification. Everything in the framework should be handled in bulk. But when in the context of a UI controller, the input and output is often singular. The controller then has to convert it into bulk and then retrieve the bulk and convert it back to singular.

We then have to do something like:

public Account doSomeOperation(Account record)
{
  return Accounts.newInstance( new List<record> {record} )
      .someOperation()
      .getRecords()
      .get(0);
}

or we can have overloaded methods doing that conversion for us, doing something like:

public Account doSomeOperation(Account record)
{
  return Accounts.newInstance(record)
      .someOperation()
      .getFirstObject();
}

The idea was to limit code duplication and to write less code. The fflib frame-work then takes care of it.

wimvelzeboer commented 2 years ago

Apex Commons is a foundation on which teams can build their implementations, including more convenient methods. I'm opposed to this being herein.

Sure, no problem. I removed the method!