Skip to content

Commit

Permalink
Refactored again - core logic lives in abstract class SObjectRepository
Browse files Browse the repository at this point in the history
Implement a refactor of my recent refactor. Perhaps create IRefactorFactory for future refactor scalability

* Started to refactor my refactor from earlier this week

- This is starting to grow bigger than I anticipated, but it's fun, so I'll proceed for now
- This refactor is all new classes - the old refactor has been left untouched for now

* Changed orderBy() to build store the individual 'order by' field strings, instead of trying to track the field & sort

* Simplified how conditions are added

- Ran into problems with scope & variable binding, so I'm now taking the approach of delegating the query execution to the concrete SObject repository classes so they can execute the queries with the variables in context.
- Tried to show how to leverage the Schema namespace to reference fields so that custom fields can't be deleted when reference by a repo class

* Added control for sorting nulls

* Fixed a bug with the getLeadById method

If only there was a way to write automated tests that could check these things for me... *sign* perhaps one day :-P

* Added SObjectRepository.addCommonQueryFields()

This adds some of the common SF fields to the set, if they exist for the specified SObject Type

* Fixed a comment, added a line break for readability

* Forgot to add StringUtils.cls

* Minified the enums - no need for them to take up so many lines

* Renamed parse to addFieldSetMembers

This makes it consistent with addCommonQueryFields()

* Added a boolean to disable adding the common fields

Only fields in the field set will be included in the query

* Relocated and renamed buildQuery() to getQuery()

* Updated calls from buildQuery() to getQuery()

* Added an example of disabling the common query fields in LeadRepository.cls

* Changed which sample static method calls setAsUpdate()

* Forgot to commit the latest version of ISObjectRepository.cls

* Added a second field set to Lead.object

* Forgot to commit CollectionUtils.cls

* Made things 47.3% less silly - scrapped using static methods

Now, any packaged query methods should be added as public methods. These should call any private/protected builder methods and return the results of the query, casted to the appropriate data type

* Changed some method names in LeadRepository.cls

Playing around with what naming convention would work best

* Added TaskRepository.cls as another example class

* Added 'IsClosed' to the list of common fields (available on task, opportunity & case)

* Added a comment to LeadRepository.cls to describe usage

If you don't want external code to have this control, only have make the LeadRepository() constructor public and internally set the field set to use

* Fixed SObjectRepository so FOR UPDATE is only used when there is no ORDER BY

I was unintenionally checking limitCount instead of the orderByList

* Fixed the getList method so it actually... ya know, returns a list

* Deleted the previous implementation of the project because I hate it now

* Added CollectionUtils_Test.cls

* Updated CollectionUtils_Test.cls to API v38.0

* Added new test classes, finished some existing test classes

* Added another test method in LeadRepository_Tests.cls
  • Loading branch information
jongpie authored Feb 16, 2017
1 parent 8ca19a1 commit dad67a4
Show file tree
Hide file tree
Showing 22 changed files with 516 additions and 136 deletions.
34 changes: 34 additions & 0 deletions src/classes/CollectionUtils.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
public without sharing class CollectionUtils {

public static String toString(List<SObject> recordList) {
List<String> recordIdStringList = new List<String>();
for(SObject record : recordList) recordIdStringList.add(record.Id);
return toString(recordIdStringList);
}

public static String toString(Set<Id> recordIdSet) {
List<String> stringList = new List<String>();
for(Id recordId : new List<Id>(recordIdSet)) stringList.add(recordId);
return toString(stringList);
}

public static String toString(Set<String> stringSet) {
return toString(new List<String>(stringSet));
}

public static String toString(List<String> stringList) {
return toString(stringList, true);
}

public static String toString(List<String> stringList, Boolean wrapWithParentheses) {
List<String> parsedStringList = new List<String>();
for(String str : stringList) parsedStringList.add('\'' + str + '\'');

String parsedString = String.join(parsedStringList, ',');

if(wrapWithParentheses) parsedString = '(' + parsedString + ')';

return parsedString;
}

}
File renamed without changes.
71 changes: 71 additions & 0 deletions src/classes/CollectionUtils_Test.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@isTest
private class CollectionUtils_Test {

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

insert leadToInsertList;
}

@isTest
static void toString_WHEN_recordList() {
List<Lead> leadList = [SELECT Id FROM Lead];
List<Id> leadIdList = new List<Id>(new Map<Id, Lead>(leadList).keySet());

String expectedString = '(\'' + String.join(leadIdList, '\',\'') + '\')';
String returnedString = CollectionUtils.toString(leadList);

System.assertEquals(expectedString, returnedString);
}

@isTest
static void toString_WHEN_recordIdSet() {
List<Lead> leadList = [SELECT Id FROM Lead];
List<Id> leadIdList = new List<Id>(new Map<Id, Lead>(leadList).keySet());

String expectedString = '(\'' + String.join(leadIdList, '\',\'') + '\')';
String returnedString = CollectionUtils.toString(new Map<Id, Lead>(leadList).keySet());

System.assertEquals(expectedString, returnedString);
}

@isTest
static void toString_WHEN_recordIdList() {
List<Lead> leadList = [SELECT Id FROM Lead];
List<Id> leadIdList = new List<Id>(new Map<Id, Lead>(leadList).keySet());

String expectedString = '(\'' + String.join(leadIdList, '\',\'') + '\')';
String returnedString = CollectionUtils.toString(leadIdList);

System.assertEquals(expectedString, returnedString);
}

@isTest
static void toString_WHEN_stringSet() {
Set<String> stringSet = new Set<String>{'Hola', 'Hello'};

String expectedString = '(\'' + String.join(new List<String>(stringSet), '\',\'') + '\')';
String returnedString = CollectionUtils.toString(stringSet);

System.assertEquals(expectedString, returnedString);
}

@isTest
static void toString_WHEN_stringList() {
List<String> stringList = new List<String>{'Hola', 'Hello'};

String expectedString = '(\'' + String.join(stringList, '\',\'') + '\')';
String returnedString = CollectionUtils.toString(stringList);

System.assertEquals(expectedString, returnedString);
}

}
File renamed without changes.
6 changes: 6 additions & 0 deletions src/classes/ISObjectRepository.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public interface ISObjectRepository {

SObject getRecord(Id recordId);
List<SObject> getList(List<Id> recordIdList);

}
File renamed without changes.
50 changes: 0 additions & 50 deletions src/classes/LeadQueryRepository.cls

This file was deleted.

66 changes: 66 additions & 0 deletions src/classes/LeadRepository.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
public without sharing class LeadRepository extends SObjectRepository {

private static final Schema.FieldSet DEFAULT_FIELD_SET = SObjectType.Lead.FieldSets.MyFieldSet;

public LeadRepository() {
super(LeadRepository.DEFAULT_FIELD_SET);
}

// Overload the constructor if you want to allow other code to specify the field set used
public LeadRepository(Schema.FieldSet fieldSet, Boolean addCommonQueryFields) {
super(fieldSet, addCommonQueryFields);
}

// ISObjectRepository requires at least 2 methods, getRecord & getList
public Lead getRecord(Id leadId) {
String query = this
.addConditionIdEquals(leadId)
.setAsUpdate(true)
.getQuery();

return (Lead)Database.query(query)[0];
}

public List<Lead> getList(List<Id> leadIdList) {
String query = this
.addConditionIdIn(leadIdList)
.setAsUpdate(true)
.getQuery();

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

// Add public methods needed that return the query results
// Only methods that return an SObject or collection of SObjects should be made public
public List<Lead> getListForSources(List<String> leadSourceList) {
String query = this
.addCondition(Schema.Lead.LeadSource + ' IN ' + CollectionUtils.toString(leadSourceList))
.orderBy(Schema.Lead.CreatedDate)
.getQuery();

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

public List<Lead> getListForStatus(String status, Integer limitCount) {
String query = this
.addConditionIsConverted(false)
.addConditionStatusEquals(status)
.limitCount(limitCount)
.orderBy(Schema.Lead.LastModifiedDate, SObjectRepository.SortOrder.DESCENDING)
.setAsUpdate(true)
.getQuery();

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

// 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) {
return (LeadRepository)this.addCondition(Schema.Lead.IsConverted + ' = ' + bool);
}

private LeadRepository addConditionStatusEquals(String status) {
return (LeadRepository)this.addCondition(Schema.Lead.Status + ' = ' + StringUtils.wrapInSingleQuotes(status));
}

}
5 changes: 5 additions & 0 deletions src/classes/LeadRepository.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>
88 changes: 88 additions & 0 deletions src/classes/LeadRepository_Tests.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
@isTest
private class LeadRepository_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;
}

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

Test.startTest();

Lead returnedLead = new LeadRepository().getRecord(expectedLead.Id);
System.assertEquals(expectedLead.Id, returnedLead.Id);

Test.stopTest();
}

@isTest
static void getRecord_WHEN_fieldSetIsSpecified() {
Schema.FieldSet expectedFieldSet = SObjectType.Lead.FieldSets.AnotherFieldSet;
Lead expectedLead = [SELECT Id FROM Lead LIMIT 1];

Test.startTest();

Lead returnedLead = new LeadRepository(expectedFieldSet, false).getRecord(expectedLead.Id);
System.assertEquals(expectedLead.Id, returnedLead.Id);

Test.stopTest();
}

@isTest
static void getList() {
List<Lead> expectedLeadList = [SELECT Id FROM Lead];
List<Id> expectedLeadIdList = new List<Id>(new Map<Id, Lead>(expectedLeadList).keySet());

Test.startTest();

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

Test.stopTest();
}

@isTest
static void getListForSources() {
String expectedLeadSource = 'GitHub';

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

Integer leadCountForExpectedLeadSource = [SELECT COUNT() FROM Lead WHERE LeadSource = :expectedLeadSource];

Test.startTest();

List<Lead> returnedLeadList = new LeadRepository().getListForSources(new List<String>{expectedLeadSource});
System.assertEquals(leadCountForExpectedLeadSource, returnedLeadList.size());

Test.stopTest();
}

@isTest
static void getListForStatus() {
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;

Test.startTest();

List<Lead> returnedLeadList = new LeadRepository().getListForStatus(expectedStatus, limitCount);
System.assertEquals(limitCount, returnedLeadList.size());

Test.stopTest();
}

}
5 changes: 5 additions & 0 deletions src/classes/LeadRepository_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>
42 changes: 0 additions & 42 deletions src/classes/QueryGenerator.cls

This file was deleted.

Loading

0 comments on commit dad67a4

Please sign in to comment.