Skip to content

Conversation

@squattingdog
Copy link
Contributor

@squattingdog squattingdog commented Sep 1, 2016

When a polymorphic field is being used and the type chosen does not match the sObjectType set in the selector, the configureQueryFactoryFields method will fail.

Add the ability to set the sObjectType when validating the field exists on the object. Use the type set in the selector to determine which polymorphic type to use.

Example: Lead.Owner.UserRoleID
Owner is GROUP | USER
Group is the first type returned and does not contain UserRoleID. The method then fails when the getField result is null.


This change is Reviewable

…he type specified in the selector.

Add the ability to set the sObjectType when validating the field exists on the object.  Use the type set in the selector to determine which polymorphic type to use.

Example: Lead.Owner.UserRoleID
Owner is GROUP | USER
Group does not contain UserRoleID and fails when the getField call sets the sObjectField token to null.
@dfruddffdc
Copy link
Contributor

Actually, polymorphic fields in Salesforce are a special case.

Lead.Owner can relate to a Queue or a User.
But crucially, the queryable fields don't map to either Queue or User.

With a bit of experimenting and googling, it turns out that the related object type is an SObjectType called Name. It's a coincidence that there are fields with the same name on the User and Name SObjectTypes. It would be a dangerous assumption to say all fields on the User SObjectType will also exist on Name.

So I think the fix is smaller, and shouldn't require any changes to method signatures. Just when trying to get the describe for the lookup referenceTo, it should be something like:
SObjectType relatedSObjectType = relatedObjs.size() == 1 ? relatedObjs[0] : Name.SObjectType;

@squattingdog
Copy link
Contributor Author

In this case of Lead.Owner, the getDescribe method returns 2 values, Queue and User. Name would not match the values returned. The validation is performed using the defined selector's getSObjectType method to validate that the field exists on the specified sObjectType. The UserSelector.getSObjectType returns User.getSObjectType if it were another selector, it would return the type specific to it.

For the above reason, I am not following the "dangerous assumption" statement. As stated, all the fields on User do not exist on Name. If the relatedSObjectType is set to Name, the validation will still fail when looping the fields to find a User or Queue specific field. For example, the field User.BadgeText does not exist on Name. While this proposed solution is simpler on the surface, it still contains the same error this solution solves. That is, when the process loops the fields for a given SObjectType, it may not find a valid match because the wrong type is being used for the matching process.

@dfruddffdc
Copy link
Contributor

Hi again, I didn't entirely follow this:

The validation is performed using the defined selector's getSObjectType method to validate that the field exists on the specified sObjectType. The UserSelector.getSObjectType returns User.getSObjectType if it were another selector, it would return the type specific to it.

But hopefully your own example will clarify things here.

For example, the field User.BadgeText does not exist on Name

You are correct, User has a field called BadgeText, which Name does not.
That is why this SOQL works: SELECT BadgeText FROM User
But this SOQL fails: SELECT Owner.BadgeText FROM Lead
And this SOQL passes: SELECT Owner.UserRoleId FROM Lead - because of the Name.UserRoleId field, not because of the User.UserRoleId field.

While a specific Lead record's (i.e. a single DB row) Owner is either a USER or a GROUP, the Lead SObjectType's (i.e. the whole DB table) Owner lookup is related to the Name SObjectType, not User or Group.

@dfruddffdc
Copy link
Contributor

Ah, I follow a bit more now. You are using the UserSelector to ensure you select all interesting fields about the Lead's Owner. In fact, you need a NameSelector to configure the fields on the main QueryFactory instance.

//new Testfflib_UserSelector().configureQueryFactoryFields(qf, 'Lead.Owner');
new MyNameSelector().configureQueryFactoryFields(qf, 'Lead.Owner');

So I still feel the fix would be:
SObjectType relatedSObjectType = relatedObjs.size() == 1 ? relatedObjs[0] : Name.SObjectType;

tstone added 2 commits March 7, 2017 13:16
@squattingdog
Copy link
Contributor Author

Separate note here, the SF org user creds seem to be out of sync which is causing the validation deploy to fail. @afawcett

@squattingdog
Copy link
Contributor Author

Hi David,
The issue that is being addressed is with the describe call. It fails due to the incorrect SObjectType being used; the actual soql that is generated and ultimately executed is not in question as the error happens prior to any soql being generated. If the field doesn't exist on Name then the describe still fails.

For example, if we are looping the values for Lead.Owner.BadgeText we get an array of [Owner, BadgeText] for the specific Lead sObjectType. The first iteration through we get Owner. On the describe we get back the array: [ Group, User]. Let's set the SObjectType to Name because there is more than 1 item in the array. The next iteration we are describing Name to find BadgeText, however, BadgeText does not exist on Name. This results in the same error as arbitrarily setting the sObjectType to the first item in the array.

This PR addresses the issue by passing in the sObjectType from the concrete selector that is using the QueryFactory instance. I understand that this creates a tighter coupling between the 2 classes. Perhaps, passing an instance of the concrete SObjectSelector to the QueryFactory constructor and then subsequently calling the SObjectSelector instance's getSObjectType method is a bit better??

Interested to hear any proposals for this issue.

@afawcett
Copy link
Contributor

@squattingdog i think its best to avoid tighter coupling so passing in the sobjectype feels fine to me. @dfruddffdc do you agree?

@afawcett
Copy link
Contributor

@squattingdog i appreciate this is an old PR by now, wondered if you still had a few on my last question on the coupling? I think we are close to getting agreement and merging.

@squattingdog
Copy link
Contributor Author

@afawcett hi, sorry for the delay, I lost track of this over the holidays. Anyway, the current implementation of this PR passes the sObjectType. Are you saying that is the best option at this time?

@afawcett
Copy link
Contributor

afawcett commented Feb 4, 2019

@squattingdog indeed it does! Sorry i misread your last para from an earlier comment. @agarciaffdc this is good in my view to merge.

this.setMessage( 'Invalid field \''+fieldName+'\' for object \''+objectType+'\'' );
}
}
}public class InvalidRelationshipException extends Exception{
Copy link
Contributor

@agarciaffdc agarciaffdc Feb 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@squattingdog sorry for asking this when your PR is opened since so long ago. But could you clean format a bit? This class should be at least one line below the } And we usually add open brace below the line instead of beside. For instance:

}public class InvalidRelationshipException extends Exception{

should be:

}
public class InvalidRelationshipException extends Exception
{

Don't worry about the code it is not yours. We will merge it as soon as you format it a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, not sure how that happened...addressing those.

@agarciaffdc
Copy link
Contributor

@dbtavernerffdc FYI

@squattingdog
Copy link
Contributor Author

FYI, there still seems to be an issue with the creds being used for the Travis CI builds.

/home/travis/build/financialforcedev/fflib-apex-common/lib/exec_anon.xml:147: INVALID_LOGIN: Invalid username, password, security token; or user locked out.

@agarciaffdc
Copy link
Contributor

Thanks for the hands up @squattingdog We will work around it in order to be able to merge it without Travis confirmation on your fork. Then Travis works properly on the lib repo

}
}

public class InvalidRelationshipException extends Exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find anywhere else in this pull request where this exception is referenced. Perhaps it should be thrown if the passed in object type is not in the reference to list.

Copy link
Contributor

@agarciaffdc agarciaffdc Feb 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one!! @squattingdog could you check this one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@squattingdog once you check this one we will merge it quickly.

if(relatedObjs.size() > 1) {
String relatedSObjectName = fflib_SObjectDescribe.getDescribe(relatedSObjectType).getDescribe().getName();
for(Schema.sObjectType sot : relatedObjs) {
if(fflib_SObjectDescribe.getDescribe(sot).getDescribe().getName() == relatedSObjectName){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest why are we comparing the names and not just the sobject type objects?

@JAertgeerts
Copy link
Contributor

@afawcett @ImJohnMDaniel can this be looked at? This is a pretty significant issue for any field (and increasing with the increase of polymorphic fields added with Industry clouds) that can have multiple objects as a related object.

daveespo added a commit that referenced this pull request Apr 5, 2022
…ans up the couple of issues outlined on the prior PR comment thread (removes unused Exception and uses the SObjectType for the comparison vs. the String name)
Copy link
Contributor

@daveespo daveespo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 3 files reviewed, 4 unresolved discussions (waiting on @agarciaffdc, @daveespo, @dbtavernerffdc, and @squattingdog)

a discussion (no related file):
I created a new PR for this change request over in #402 since this PR is against the old mdapi format project structure and has conflicts that are too hard to resolve

Additionally, I addressed several comment threads below in that other branch so I'm marking them as resolved here and closing this PR without merging it.



fflib/src/classes/fflib_QueryFactory.cls, line 118 at r4 (raw file):

Previously, dbtavernerffdc wrote…

Out of interest why are we comparing the names and not just the sobject type objects?

I agree -- I changed this to compare the SObjectType and it properly compiled and all tests passed


fflib/src/classes/fflib_QueryFactory.cls, line 794 at r4 (raw file):

Previously, agarciaffdc wrote…

@squattingdog once you check this one we will merge it quickly.

This Exception seems to be unused so I removed it from the code base

@daveespo daveespo closed this Apr 5, 2022
daveespo added a commit that referenced this pull request Apr 5, 2022
Redux of PR #137 - rebased against latest master and merge conflicts resolved along with the two outstanding comments on the prior PR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants