Skip to content

Commit c40f63f

Browse files
authored
Implement dynamic SOSL (#12)
Added the metho SObjectRepository.getSearchQuery(String searchTerm, SObjectRepository.SearchGroup searchGroup); Made several private methods to build to the individual components. getQuery and getSearchQuery methods call these methods where needed to build each type of query string Added new enum, SObjectRepository.SearchGroup. This is used to control what fields are searched for SOSL queries Added ISObjectRespository.List<SObject> searchInAllFields(String searchTerm); to require one basic SOSL method per object Renamed/tweaked some existing methods Added some missing & new test classes/methods
1 parent 891a4ad commit c40f63f

File tree

7 files changed

+188
-22
lines changed

7 files changed

+188
-22
lines changed

src/classes/ISObjectRepository.cls

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
public interface ISObjectRepository {
22

3+
// SOQL
34
SObject getRecord(Id recordId);
45
List<SObject> getList(List<Id> recordIdList);
6+
// SOSL
7+
List<SObject> searchInAllFields(String searchTerm);
58

69
}

src/classes/LeadRepository.cls

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public without sharing class LeadRepository extends SObjectRepository {
1515
public Lead getRecord(Id leadId) {
1616
String query = this
1717
.addConditionIdEquals(leadId)
18-
.setAsUpdate(true)
18+
.setAsUpdate()
1919
.getQuery();
2020

2121
return (Lead)Database.query(query)[0];
@@ -24,7 +24,7 @@ public without sharing class LeadRepository extends SObjectRepository {
2424
public List<Lead> getList(List<Id> leadIdList) {
2525
String query = this
2626
.addConditionIdIn(leadIdList)
27-
.setAsUpdate(true)
27+
.setAsUpdate()
2828
.getQuery();
2929

3030
return (List<Lead>)Database.query(query);
@@ -47,12 +47,23 @@ public without sharing class LeadRepository extends SObjectRepository {
4747
.addConditionStatusEquals(status)
4848
.limitCount(limitCount)
4949
.orderBy(Schema.Lead.LastModifiedDate, SObjectRepository.SortOrder.DESCENDING)
50-
.setAsUpdate(true)
50+
.setAsUpdate()
5151
.getQuery();
5252

5353
return (List<Lead>)Database.query(query);
5454
}
5555

56+
public List<Lead> searchInAllFields(String searchTerm) {
57+
String query = this
58+
.addConditionIsConverted(false)
59+
.orderBy(Schema.Lead.CreatedDate, SObjectRepository.SortOrder.DESCENDING)
60+
.limitCount(10)
61+
.setAsUpdate() // SOSL cannot use FOR UPDATE. This will execute, but a warning debug statement will indicate that it is ignored
62+
.getSearchQuery(searchTerm, SObjectRepository.SearchGroup.ALL_FIELDS);
63+
64+
return (List<Lead>)Search.query(query)[0];
65+
}
66+
5667
// You can add additional builder methods for any commonly used filters for this SObject
5768
// All builder methods should be kept as private or protected
5869
private LeadRepository addConditionIsConverted(Boolean bool) {

src/classes/LeadRepository_Tests.cls

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ private class LeadRepository_Tests {
5656
static void getListForSources() {
5757
String expectedLeadSource = 'GitHub';
5858

59-
List<Lead> leadList = [SELECT Id, LeadSource FROM Lead LIMIT 2];
59+
List<Lead> leadList = [SELECT Id, LeadSource FROM Lead LIMIT 2];
6060
for(Lead lead : leadList) lead.LeadSource = expectedLeadSource;
6161
update leadList;
6262

@@ -72,7 +72,7 @@ private class LeadRepository_Tests {
7272

7373
@isTest
7474
static void getListForStatus() {
75-
String expectedStatus = [SELECT Status FROM Lead LIMIT 1].Status;
75+
String expectedStatus = [SELECT Status FROM Lead LIMIT 1].Status;
7676
Integer leadCountForExpectedStatus = [SELECT COUNT() FROM Lead WHERE Status = :expectedStatus];
7777
System.assert(leadCountForExpectedStatus > 0);
7878
Integer limitCount = leadCountForExpectedStatus - 1;
@@ -85,4 +85,17 @@ private class LeadRepository_Tests {
8585
Test.stopTest();
8686
}
8787

88+
@isTest
89+
static void searchInAllFields() {
90+
String searchTerm = [SELECT LastName FROM Lead WHERE LastName != null LIMIT 1].LastName;
91+
List<Lead> expectedLeadList = (List<Lead>)[FIND :searchTerm IN ALL FIELDS RETURNING Lead(Id WHERE IsConverted = false)][0];
92+
93+
Test.startTest();
94+
95+
List<Lead> returnedLeadList = new LeadRepository().searchInAllFields(searchTerm);
96+
System.assertEquals(expectedLeadList.size(), returnedLeadList.size());
97+
98+
Test.stopTest();
99+
}
100+
88101
}

src/classes/SObjectRepository.cls

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ public abstract class SObjectRepository implements ISObjectRepository {
33
public enum SortOrder { ASCENDING, DESCENDING }
44
public enum NullsSortOrder { FIRST, LAST }
55

6+
public enum SearchGroup { ALL_FIELDS, NAME_FIELDS, EMAIL_FIELDS, PHONE_FIELDS, SIDEBAR_FIELDS }
7+
68
private SObjectType sobjectType;
79
private Map<String, Schema.SObjectField> sobjectTypeFieldMap;
810
private Set<String> queryFields;
@@ -75,25 +77,35 @@ public abstract class SObjectRepository implements ISObjectRepository {
7577
return this;
7678
}
7779

78-
protected SObjectRepository setAsUpdate(Boolean bool) {
79-
this.forUpdate = bool;
80+
protected SObjectRepository setAsUpdate() {
81+
this.forUpdate = true;
8082
return this;
8183
}
8284

8385
protected String getQuery() {
84-
this.query = 'SELECT ' + String.join(new List<String>(this.queryFields), ', ')
85-
+ ' FROM ' + this.sobjectType;
86+
this.query = 'SELECT ' + this.getQueryFieldString()
87+
+ ' FROM ' + this.sobjectType
88+
+ this.getWhereClauseString()
89+
+ this.getOrderByString()
90+
+ this.getLimitCountString()
91+
+ this.getForUpdateString();
92+
93+
System.debug(this.query);
8694

87-
// Generate the WHERE clause
88-
if(!this.whereClauseList.isEmpty()) this.query += ' WHERE ' + String.join(this.whereClauseList, ' AND ');
95+
return this.query;
96+
}
8997

90-
// Generate the ORDER BY clause
91-
if(!this.orderByList.isEmpty()) this.query += ' ORDER BY ' + String.join(new List<String>(orderByList), ', ');
98+
protected String getSearchQuery(String searchTerm, SObjectRepository.SearchGroup searchGroup) {
99+
this.query = 'FIND ' + StringUtils.wrapInSingleQuotes(searchTerm)
100+
+ ' IN ' + searchGroup.name().replace('_', ' ')
101+
+ ' RETURNING ' + this.sobjectType + '('
102+
+ this.getQueryFieldString()
103+
+ this.getWhereClauseString()
104+
+ this.getOrderByString()
105+
+ this.getLimitCountString()
106+
+ ')';
92107

93-
// Add the LIMIT if provided
94-
if(this.limitCount != null) this.query += ' LIMIT '+ this.limitCount;
95-
// Mark the query as FOR UPDATE if true. You can't use ORDER BY and FOR UPDATE together
96-
if(this.orderByList.isEmpty() && this.forUpdate) this.query += ' FOR UPDATE';
108+
if(this.forUpdate) System.debug(LoggingLevel.WARN, 'getSearchQuery method flagged as FOR UPDATE. SOSL cannot use FOR UPDATE, ignoring');
97109

98110
System.debug(this.query);
99111

@@ -121,4 +133,32 @@ public abstract class SObjectRepository implements ISObjectRepository {
121133
for(Schema.FieldSetMember field : this.fieldSet.getFields()) this.queryFields.add(field.getFieldPath());
122134
}
123135

136+
private String getQueryFieldString() {
137+
return String.join(new List<String>(this.queryFields), ',');
138+
}
139+
140+
private String getWhereClauseString() {
141+
String whereClauseString = '';
142+
if(!this.whereClauseList.isEmpty()) whereClauseString = ' WHERE ' + String.join(this.whereClauseList, ' AND ');
143+
return whereClauseString;
144+
}
145+
146+
private String getOrderByString() {
147+
String orderByString = '';
148+
if(!this.orderByList.isEmpty()) orderByString = ' ORDER BY ' + String.join(new List<String>(orderByList), ', ');
149+
return orderByString;
150+
}
151+
152+
private String getLimitCountString() {
153+
String limitString = '';
154+
if(this.limitCount != null) limitString = ' LIMIT '+ this.limitCount;
155+
return limitString;
156+
}
157+
158+
private String getForUpdateString() {
159+
String forUpdateString = '';
160+
if(this.orderByList.isEmpty() && this.forUpdate) forUpdateString = ' FOR UPDATE';
161+
return forUpdateString;
162+
}
163+
124164
}

src/classes/TaskRepository.cls

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public without sharing class TaskRepository extends SObjectRepository {
99
public Task getRecord(Id taskId) {
1010
String query = this
1111
.addConditionIdEquals(taskId)
12-
.setAsUpdate(true)
12+
.setAsUpdate()
1313
.getQuery();
1414

1515
return (Task)Database.query(query)[0];
@@ -18,17 +18,17 @@ public without sharing class TaskRepository extends SObjectRepository {
1818
public List<Task> getList(List<Id> taskIdList) {
1919
String query = this
2020
.addConditionIdIn(taskIdList)
21-
.setAsUpdate(true)
21+
.setAsUpdate()
2222
.getQuery();
2323

2424
return (List<Task>)Database.query(query);
2525
}
2626

27-
public List<Task> getListOfOpenByWhoId(Id whoId) {
28-
return getListOfOpenByWhoId(new List<Id>{whoId});
27+
public List<Task> getListOfOpenForWhoId(Id whoId) {
28+
return getListOfOpenForWhoId(new List<Id>{whoId});
2929
}
3030

31-
public List<Task> getListOfOpenByWhoId(List<Id> whoIdList) {
31+
public List<Task> getListOfOpenForWhoId(List<Id> whoIdList) {
3232
String query = this
3333
.addConditionWhoIdIn(whoIdList)
3434
.addConditionIsClosed(false)
@@ -39,6 +39,16 @@ public without sharing class TaskRepository extends SObjectRepository {
3939
return (List<Task>)Database.query(query);
4040
}
4141

42+
public List<Task> searchInAllFields(String searchTerm) {
43+
String query = this
44+
.addConditionIsClosed(false)
45+
.orderBy(Schema.Task.WhoId)
46+
.limitCount(10)
47+
.getSearchQuery(searchTerm, SObjectRepository.SearchGroup.ALL_FIELDS);
48+
49+
return (List<Task>)Search.query(query)[0];
50+
}
51+
4252
private TaskRepository addConditionIsClosed(Boolean bool) {
4353
return (TaskRepository)this.addCondition(Schema.Task.IsClosed + ' = ' + bool);
4454
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@isTest
2+
private class TaskRepository_Tests {
3+
4+
@testSetup
5+
static void setup() {
6+
List<Lead> leadList = new List<Lead>();
7+
for(Integer i = 0; i < 5; i++) {
8+
Lead lead = new Lead(
9+
Company = 'My Test Company',
10+
LastName = 'Gillespie'
11+
);
12+
leadList.add(lead);
13+
}
14+
insert leadList;
15+
16+
List<Task> taskList = new List<Task>();
17+
for(Lead lead : leadList) {
18+
Task task = new Task(
19+
Description = 'Call about the thing',
20+
Status = 'Not Started',
21+
WhoId = lead.Id
22+
);
23+
taskList.add(task);
24+
}
25+
insert taskList;
26+
}
27+
28+
@isTest
29+
static void getRecord() {
30+
Task expectedTask = [SELECT Id FROM Task LIMIT 1];
31+
32+
Test.startTest();
33+
34+
Task returnedTask = new TaskRepository().getRecord(expectedTask.Id);
35+
System.assertEquals(expectedTask.Id, returnedTask.Id);
36+
37+
Test.stopTest();
38+
}
39+
40+
@isTest
41+
static void getList() {
42+
List<Task> expectedTaskList = [SELECT Id FROM Task];
43+
List<Id> expectedTaskIdList = new List<Id>(new Map<Id, Task>(expectedTaskList).keySet());
44+
45+
Test.startTest();
46+
47+
List<Task> returnedTaskList = new TaskRepository().getList(expectedTaskIdList);
48+
System.assertEquals(expectedTaskList.size(), returnedTaskList.size());
49+
50+
Test.stopTest();
51+
}
52+
53+
@isTest
54+
static void getListOfOpenForWhoId() {
55+
Lead lead = [SELECT Id FROM Lead LIMIT 1];
56+
57+
Map<Id, Task> expectedTaskMap = new Map<Id, Task>([SELECT Id, WhoId FROM Task WHERE WhoId = :lead.Id AND IsClosed = false]);
58+
System.assert(expectedTaskMap.size() > 0);
59+
60+
Test.startTest();
61+
62+
Map<Id, Task> returnedTaskMap = new Map<Id, Task>(new TaskRepository().getListOfOpenForWhoId(lead.Id));
63+
System.assertEquals(returnedTaskMap.size(), returnedTaskMap.size());
64+
for(Id expectedTaskId : expectedTaskMap.keySet()) {
65+
System.assert(returnedTaskMap.containsKey(expectedTaskId));
66+
}
67+
68+
Test.stopTest();
69+
}
70+
71+
@isTest
72+
static void searchInAllFields() {
73+
String searchTerm = 'thing';
74+
List<Task> expectedTaskList = (List<Task>)[FIND :searchTerm IN ALL FIELDS RETURNING Task(Id WHERE IsClosed = false)][0];
75+
76+
Test.startTest();
77+
78+
List<Task> returnedTaskList = new TaskRepository().searchInAllFields(searchTerm);
79+
System.assertEquals(expectedTaskList.size(), returnedTaskList.size());
80+
81+
Test.stopTest();
82+
}
83+
84+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>38.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>

0 commit comments

Comments
 (0)