Skip to content

Commit

Permalink
Implement dynamic SOSL (#12)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jongpie authored Feb 19, 2017
1 parent 891a4ad commit c40f63f
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 22 deletions.
3 changes: 3 additions & 0 deletions src/classes/ISObjectRepository.cls
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
public interface ISObjectRepository {

// SOQL
SObject getRecord(Id recordId);
List<SObject> getList(List<Id> recordIdList);
// SOSL
List<SObject> searchInAllFields(String searchTerm);

}
17 changes: 14 additions & 3 deletions src/classes/LeadRepository.cls
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public without sharing class LeadRepository extends SObjectRepository {
public Lead getRecord(Id leadId) {
String query = this
.addConditionIdEquals(leadId)
.setAsUpdate(true)
.setAsUpdate()
.getQuery();

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

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

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

public List<Lead> searchInAllFields(String searchTerm) {
String query = this
.addConditionIsConverted(false)
.orderBy(Schema.Lead.CreatedDate, SObjectRepository.SortOrder.DESCENDING)
.limitCount(10)
.setAsUpdate() // SOSL cannot use FOR UPDATE. This will execute, but a warning debug statement will indicate that it is ignored
.getSearchQuery(searchTerm, SObjectRepository.SearchGroup.ALL_FIELDS);

return (List<Lead>)Search.query(query)[0];
}

// You can add additional builder methods for any commonly used filters for this SObject
// All builder methods should be kept as private or protected
private LeadRepository addConditionIsConverted(Boolean bool) {
Expand Down
17 changes: 15 additions & 2 deletions src/classes/LeadRepository_Tests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private class LeadRepository_Tests {
static void getListForSources() {
String expectedLeadSource = 'GitHub';

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

Expand All @@ -72,7 +72,7 @@ private class LeadRepository_Tests {

@isTest
static void getListForStatus() {
String expectedStatus = [SELECT Status FROM Lead LIMIT 1].Status;
String expectedStatus = [SELECT Status FROM Lead LIMIT 1].Status;
Integer leadCountForExpectedStatus = [SELECT COUNT() FROM Lead WHERE Status = :expectedStatus];
System.assert(leadCountForExpectedStatus > 0);
Integer limitCount = leadCountForExpectedStatus - 1;
Expand All @@ -85,4 +85,17 @@ private class LeadRepository_Tests {
Test.stopTest();
}

@isTest
static void searchInAllFields() {
String searchTerm = [SELECT LastName FROM Lead WHERE LastName != null LIMIT 1].LastName;
List<Lead> expectedLeadList = (List<Lead>)[FIND :searchTerm IN ALL FIELDS RETURNING Lead(Id WHERE IsConverted = false)][0];

Test.startTest();

List<Lead> returnedLeadList = new LeadRepository().searchInAllFields(searchTerm);
System.assertEquals(expectedLeadList.size(), returnedLeadList.size());

Test.stopTest();
}

}
64 changes: 52 additions & 12 deletions src/classes/SObjectRepository.cls
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ public abstract class SObjectRepository implements ISObjectRepository {
public enum SortOrder { ASCENDING, DESCENDING }
public enum NullsSortOrder { FIRST, LAST }

public enum SearchGroup { ALL_FIELDS, NAME_FIELDS, EMAIL_FIELDS, PHONE_FIELDS, SIDEBAR_FIELDS }

private SObjectType sobjectType;
private Map<String, Schema.SObjectField> sobjectTypeFieldMap;
private Set<String> queryFields;
Expand Down Expand Up @@ -75,25 +77,35 @@ public abstract class SObjectRepository implements ISObjectRepository {
return this;
}

protected SObjectRepository setAsUpdate(Boolean bool) {
this.forUpdate = bool;
protected SObjectRepository setAsUpdate() {
this.forUpdate = true;
return this;
}

protected String getQuery() {
this.query = 'SELECT ' + String.join(new List<String>(this.queryFields), ', ')
+ ' FROM ' + this.sobjectType;
this.query = 'SELECT ' + this.getQueryFieldString()
+ ' FROM ' + this.sobjectType
+ this.getWhereClauseString()
+ this.getOrderByString()
+ this.getLimitCountString()
+ this.getForUpdateString();

System.debug(this.query);

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

// Generate the ORDER BY clause
if(!this.orderByList.isEmpty()) this.query += ' ORDER BY ' + String.join(new List<String>(orderByList), ', ');
protected String getSearchQuery(String searchTerm, SObjectRepository.SearchGroup searchGroup) {
this.query = 'FIND ' + StringUtils.wrapInSingleQuotes(searchTerm)
+ ' IN ' + searchGroup.name().replace('_', ' ')
+ ' RETURNING ' + this.sobjectType + '('
+ this.getQueryFieldString()
+ this.getWhereClauseString()
+ this.getOrderByString()
+ this.getLimitCountString()
+ ')';

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

System.debug(this.query);

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

private String getQueryFieldString() {
return String.join(new List<String>(this.queryFields), ',');
}

private String getWhereClauseString() {
String whereClauseString = '';
if(!this.whereClauseList.isEmpty()) whereClauseString = ' WHERE ' + String.join(this.whereClauseList, ' AND ');
return whereClauseString;
}

private String getOrderByString() {
String orderByString = '';
if(!this.orderByList.isEmpty()) orderByString = ' ORDER BY ' + String.join(new List<String>(orderByList), ', ');
return orderByString;
}

private String getLimitCountString() {
String limitString = '';
if(this.limitCount != null) limitString = ' LIMIT '+ this.limitCount;
return limitString;
}

private String getForUpdateString() {
String forUpdateString = '';
if(this.orderByList.isEmpty() && this.forUpdate) forUpdateString = ' FOR UPDATE';
return forUpdateString;
}

}
20 changes: 15 additions & 5 deletions src/classes/TaskRepository.cls
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public without sharing class TaskRepository extends SObjectRepository {
public Task getRecord(Id taskId) {
String query = this
.addConditionIdEquals(taskId)
.setAsUpdate(true)
.setAsUpdate()
.getQuery();

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

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

public List<Task> getListOfOpenByWhoId(Id whoId) {
return getListOfOpenByWhoId(new List<Id>{whoId});
public List<Task> getListOfOpenForWhoId(Id whoId) {
return getListOfOpenForWhoId(new List<Id>{whoId});
}

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

public List<Task> searchInAllFields(String searchTerm) {
String query = this
.addConditionIsClosed(false)
.orderBy(Schema.Task.WhoId)
.limitCount(10)
.getSearchQuery(searchTerm, SObjectRepository.SearchGroup.ALL_FIELDS);

return (List<Task>)Search.query(query)[0];
}

private TaskRepository addConditionIsClosed(Boolean bool) {
return (TaskRepository)this.addCondition(Schema.Task.IsClosed + ' = ' + bool);
}
Expand Down
84 changes: 84 additions & 0 deletions src/classes/TaskRepository_Tests.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@isTest
private class TaskRepository_Tests {

@testSetup
static void setup() {
List<Lead> leadList = new List<Lead>();
for(Integer i = 0; i < 5; i++) {
Lead lead = new Lead(
Company = 'My Test Company',
LastName = 'Gillespie'
);
leadList.add(lead);
}
insert leadList;

List<Task> taskList = new List<Task>();
for(Lead lead : leadList) {
Task task = new Task(
Description = 'Call about the thing',
Status = 'Not Started',
WhoId = lead.Id
);
taskList.add(task);
}
insert taskList;
}

@isTest
static void getRecord() {
Task expectedTask = [SELECT Id FROM Task LIMIT 1];

Test.startTest();

Task returnedTask = new TaskRepository().getRecord(expectedTask.Id);
System.assertEquals(expectedTask.Id, returnedTask.Id);

Test.stopTest();
}

@isTest
static void getList() {
List<Task> expectedTaskList = [SELECT Id FROM Task];
List<Id> expectedTaskIdList = new List<Id>(new Map<Id, Task>(expectedTaskList).keySet());

Test.startTest();

List<Task> returnedTaskList = new TaskRepository().getList(expectedTaskIdList);
System.assertEquals(expectedTaskList.size(), returnedTaskList.size());

Test.stopTest();
}

@isTest
static void getListOfOpenForWhoId() {
Lead lead = [SELECT Id FROM Lead LIMIT 1];

Map<Id, Task> expectedTaskMap = new Map<Id, Task>([SELECT Id, WhoId FROM Task WHERE WhoId = :lead.Id AND IsClosed = false]);
System.assert(expectedTaskMap.size() > 0);

Test.startTest();

Map<Id, Task> returnedTaskMap = new Map<Id, Task>(new TaskRepository().getListOfOpenForWhoId(lead.Id));
System.assertEquals(returnedTaskMap.size(), returnedTaskMap.size());
for(Id expectedTaskId : expectedTaskMap.keySet()) {
System.assert(returnedTaskMap.containsKey(expectedTaskId));
}

Test.stopTest();
}

@isTest
static void searchInAllFields() {
String searchTerm = 'thing';
List<Task> expectedTaskList = (List<Task>)[FIND :searchTerm IN ALL FIELDS RETURNING Task(Id WHERE IsClosed = false)][0];

Test.startTest();

List<Task> returnedTaskList = new TaskRepository().searchInAllFields(searchTerm);
System.assertEquals(expectedTaskList.size(), returnedTaskList.size());

Test.stopTest();
}

}
5 changes: 5 additions & 0 deletions src/classes/TaskRepository_Tests.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>38.0</apiVersion>
<status>Active</status>
</ApexClass>

0 comments on commit c40f63f

Please sign in to comment.