Skip to content

Commit 58097a2

Browse files
committed
Rebases the fix from PR apex-enterprise-patterns#137 against latest project structure and cleans 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)
2 parents 7e0891e + 823b772 commit 58097a2

File tree

3 files changed

+122
-11
lines changed

3 files changed

+122
-11
lines changed

sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
8484
private Schema.ChildRelationship relationship;
8585
private Map<Schema.ChildRelationship, fflib_QueryFactory> subselectQueryMap;
8686

87-
private String getFieldPath(String fieldName){
87+
private String getFieldPath(String fieldName, Schema.sObjectType relatedSObjectType){
8888
if(!fieldName.contains('.')){ //single field
8989
Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(table).getField(fieldName.toLowerCase());
9090
if(token == null)
@@ -107,8 +107,21 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
107107
fflib_SecurityUtils.checkFieldIsReadable(lastSObjectType, token);
108108
}
109109

110-
if(token != null && i.hasNext() && tokenDescribe.getSoapType() == Schema.SoapType.ID){
111-
lastSObjectType = tokenDescribe.getReferenceTo()[0]; //if it's polymorphic doesn't matter which one we get
110+
if (token != null && i.hasNext() && tokenDescribe.getSoapType() == Schema.SoapType.ID) {
111+
List<Schema.sObjectType> relatedObjs = tokenDescribe.getReferenceTo(); //if it's polymorphic, it matters which one we use - i.e. Lead.Owner is GROUP|USER and each has different fields.
112+
113+
if (relatedObjs.size() == 1 || relatedSObjectType == null) {
114+
lastSObjectType = relatedObjs[0]; //caller did not specify the one to use or there's only one so use the first one
115+
}
116+
else{
117+
for (Schema.sObjectType sot : relatedObjs) {
118+
if (fflib_SObjectDescribe.getDescribe(sot).getDescribe().getSObjectType() == relatedSObjectType) {
119+
lastSObjectType = sot;
120+
break;
121+
}
122+
}
123+
}
124+
112125
fieldPath.add(tokenDescribe.getRelationshipName());
113126
}else if(token != null && !i.hasNext()){
114127
fieldPath.add(tokenDescribe.getName());
@@ -122,6 +135,10 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
122135

123136
return String.join(fieldPath,'.');
124137
}
138+
139+
private String getFieldPath(String fieldName) {
140+
return this.getFieldPath(fieldName, null);
141+
}
125142

126143
@TestVisible
127144
private static String getFieldTokenPath(Schema.SObjectField field){
@@ -197,16 +214,27 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
197214
this.sortSelectFields = doSort;
198215
return this;
199216
}
200-
201217
/**
202218
* Selects a single field from the SObject specified in {@link #table}.
203219
* Selecting fields is idempotent, if this field is already selected calling this method will have no additional impact.
204220
* @param fieldName the API name of the field to add to the query's SELECT clause.
205221
**/
206222
public fflib_QueryFactory selectField(String fieldName){
207-
fields.add( getFieldPath(fieldName) );
223+
fields.add( getFieldPath(fieldName, null) );
224+
return this;
225+
}
226+
227+
/**
228+
* Selects a single field from the SObject specified in {@link #table}.
229+
* Selecting fields is idempotent, if this field is already selected calling this method will have no additional impact.
230+
* @param fieldName the API name of the field to add to the query's SELECT clause.
231+
* @param relatedSObjectType the related sObjectType to resolve polymorphic object fields.
232+
**/
233+
public fflib_QueryFactory selectField(String fieldName, Schema.sOBjectType relatedObjectType) {
234+
fields.add(getFieldPath(fieldName, relatedObjectType));
208235
return this;
209-
}
236+
}
237+
210238
/**
211239
* Selects a field, avoiding the possible ambiguity of String API names.
212240
* @see #selectField(String)
@@ -760,6 +788,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
760788
this.setMessage( 'Invalid field \''+fieldName+'\' for object \''+objectType+'\'' );
761789
}
762790
}
791+
763792
public class InvalidFieldSetException extends Exception{}
764793
public class NonReferenceFieldException extends Exception{}
765794
public class InvalidSubqueryRelationshipException extends Exception{}

sfdx-source/apex-common/main/classes/fflib_SObjectSelector.cls

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,13 @@ public abstract with sharing class fflib_SObjectSelector
380380
public void configureQueryFactoryFields(fflib_QueryFactory queryFactory, String relationshipFieldPath)
381381
{
382382
// Add fields from selector prefixing the relationship path
383-
for(SObjectField field : getSObjectFieldList())
384-
queryFactory.selectField(relationshipFieldPath + '.' + field.getDescribe().getName());
385-
// Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule)
386-
if(UserInfo.isMultiCurrencyOrganization() && CURRENCY_ISO_CODE_ENABLED)
387-
queryFactory.selectField(relationshipFieldPath+'.CurrencyIsoCode');
383+
for(SObjectField field : getSObjectFieldList()) {
384+
queryFactory.selectField(relationshipFieldPath + '.' + field.getDescribe().getName(), this.getSObjectType());
385+
}
386+
// Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule)
387+
if(UserInfo.isMultiCurrencyOrganization() && CURRENCY_ISO_CODE_ENABLED){
388+
queryFactory.selectField(relationshipFieldPath+'.CurrencyIsoCode');
389+
}
388390
}
389391

390392
/**

sfdx-source/apex-common/test/classes/fflib_SObjectSelectorTest.cls

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,5 +474,85 @@ private with sharing class fflib_SObjectSelectorTest
474474
return null;
475475
}
476476
return testUser;
477+
}
478+
479+
@isTest
480+
static void testPolymorphicSelectWithRelatedType() {
481+
//Given
482+
483+
Testfflib_CampaignMemberSelector cmSelector = new Testfflib_CampaignMemberSelector();
484+
fflib_QueryFactory qf = cmSelector.newQueryFactory();
485+
new Testfflib_LeadSelector().configureQueryFactoryFields(qf, 'Lead');
486+
new Testfflib_UserSelector().configureQueryFactoryFields(qf, 'Lead.Owner');
487+
488+
489+
Set<String> expectedSelectFields = new Set<String>{
490+
'Id', 'Status', 'Lead.Id', 'Lead.OwnerId', 'Lead.Owner.Id', 'Lead.Owner.UserRoleId'
491+
};
492+
if (UserInfo.isMultiCurrencyOrganization()) {
493+
expectedSelectFields.add('CurrencyIsoCode');
494+
}
495+
496+
//When
497+
String soql = qf.toSOQL();
498+
499+
//Then
500+
Pattern soqlPattern = Pattern.compile('SELECT (.*) FROM CampaignMember ORDER BY CreatedDate ASC NULLS FIRST ');
501+
Matcher soqlMatcher = soqlPattern.matcher(soql);
502+
soqlMatcher.matches();
503+
504+
List<String> actualSelectFields = soqlMatcher.group(1).deleteWhiteSpace().split(',');
505+
System.assertEquals(expectedSelectFields, new Set<String>(actualSelectFields));
506+
}
507+
508+
private class Testfflib_CampaignMemberSelector extends fflib_SObjectSelector {
509+
public Testfflib_CampaignMemberSelector() {
510+
super();
511+
}
512+
513+
public List<Schema.SObjectField> getSObjectFieldList() {
514+
return new List<Schema.SObjectField>{
515+
CampaignMember.Id,
516+
CampaignMember.Status
517+
};
518+
}
519+
520+
public Schema.SObjectType getSObjectType() {
521+
return CampaignMember.sObjectType;
522+
}
523+
}
524+
525+
private class Testfflib_UserSelector extends fflib_SObjectSelector {
526+
public Testfflib_UserSelector() {
527+
super();
528+
}
529+
530+
public List<Schema.SObjectField> getSObjectFieldList() {
531+
return new List<Schema.SObjectField>{
532+
User.UserRoleId,
533+
User.Id
534+
};
535+
}
536+
537+
public Schema.SObjectType getSObjectType() {
538+
return User.sObjectType;
539+
}
540+
}
541+
542+
private class Testfflib_LeadSelector extends fflib_SObjectSelector {
543+
public Testfflib_LeadSelector() {
544+
super();
545+
}
546+
547+
public List<Schema.SObjectField> getSObjectFieldList() {
548+
return new List<Schema.SObjectField>{
549+
Lead.OwnerId,
550+
Lead.Id
551+
};
552+
}
553+
554+
public Schema.SObjectType getSObjectType() {
555+
return Lead.sObjectType;
556+
}
477557
}
478558
}

0 commit comments

Comments
 (0)