Skip to content

Commit

Permalink
Brought in more changes from the example-app implementation of filter…
Browse files Browse the repository at this point in the history
… + results

* Improvements to Contract to remove stack trace entry for the Contract methods
* Add ability to define like Strings to the criteria
* Added testing for Sobject Selector
* Added protection to public AuraEnabled properties (private setters)
* Added more capabilities to SObjectUtils for examining types
  • Loading branch information
rob-baillie-ortoo committed Jan 24, 2022
1 parent 4ce9495 commit 0745ef4
Show file tree
Hide file tree
Showing 14 changed files with 636 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public virtual with sharing class fflib_Criteria
this.type = 'AND';
}

// TODO: document and test
/**
* Adds the given evalutor onto the formula stack
*/
protected void addEvaluator( Evaluator evaluator )
{
evaluators.add( evaluator );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public abstract with sharing class fflib_SObjectSelector
* Implement this method to inform the base class of the SObject (custom or standard) to be queried
**/
abstract Schema.SObjectType getSObjectType();

/**
* Implement this method to inform the base class of the common fields to be queried or listed by the base class methods
**/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public class StackTrace {
return stackTraceEntries[0];
}

@testVisible
private StackTraceEntry getEntryStackTraceEntry()
{
if ( stackTraceEntries.isEmpty() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/
public inherited sharing virtual class ortoo_Criteria extends fflib_Criteria implements ISearchCriteria // NOPMD: specified a mini-namespace to differentiate from fflib versions
{

public virtual ortoo_Criteria likeString( Schema.SObjectField field, Object value )
{
addEvaluator( new FieldEvaluator( field, fflib_Operator.LIKEx, value ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,69 +7,98 @@
*/
public abstract inherited sharing class ortoo_SobjectSelector extends fflib_SobjectSelector implements ISearchSelector // NOPMD: specified a mini-namespace to differentiate from fflib versions
{
class UnboundCountQueryException extends Exceptions.SelectorException {}

public ortoo_SobjectSelector()
{
super();
enforceFLS();
}

/**
* Configure this instance to ignore FLS when selecting data.
*
* @return fflib_SObjectSelector Itself, allowing for a fluent interface.
*/
public fflib_SObjectSelector ignoreFls()
{
m_enforceFLS = false;
return this;
}

// TODO: document
// TODO: test
// TODO: security
public SearchResults selectBySearchCriteria( ISearchConfiguration searchConfiguration, ISearchCriteria criteria, SearchWindow window, SearchOrderBy orderBy )
{
Contract.requires( criteria != null, 'selectByCriteria called with a null criteria' );

Integer countOfRecords = getCountOfRecords( criteria );

List<Sobject> resultsRecords = new List<Sobject>();

if ( countOfRecords > 0 )
{
fflib_QueryFactory queryFactory = newQueryFactory().setCondition( criteria.toSOQL() );

queryFactory.selectFields( searchConfiguration.getRequiredFields() );
queryFactory.setOffset( window.offset );
queryFactory.setLimit( window.length );
queryFactory.setOrdering( orderBy.fieldName , orderBy.direction == 'desc' ? fflib_QueryFactory.SortOrder.DESCENDING : fflib_QueryFactory.SortOrder.ASCENDING );

resultsRecords = Database.query( queryFactory.toSOQL() );
}

return new SearchResults( countOfRecords, resultsRecords );
}

// TODO: document
// TODO: test
protected Integer getCountOfRecords( ISearchCriteria soqlCriteria )
{
// TODO: security
String whereClause = soqlCriteria.toSOQL();

if ( String.isBlank( whereClause ) )
{
throw new UnboundCountQueryException( 'Attempted to perform a count on an unbound query against ' + getSObjectName() )
.setErrorCode( FrameworkErrorCodes.SELECTOR_UNBOUND_COUNT_QUERY )
.addContext( 'SObjectType', getSObjectName() );
}

String query = 'SELECT COUNT(Id) recordCount FROM ' + getSObjectName() + ' WHERE ' + whereClause;

AggregateResult result = (AggregateResult)Database.query( query ); // NOPMD: variables come from a trusted source

return (Integer)result.get( 'recordCount' );
}
public class UnboundCountQueryException extends Exceptions.SelectorException {}

public ortoo_SobjectSelector()
{
super();
enforceFLS();
}

/**
* Configure this instance to ignore FLS when selecting data.
*
* @return fflib_SObjectSelector Itself, allowing for a fluent interface.
*/
public fflib_SObjectSelector ignoreFls()
{
m_enforceFLS = false;
return this;
}

/**
* Given a configuration, search criteria, a window and an order by, will perform the defined search.
*
* The results includes a total record count as well as the windowed search results.
*
* WIll perform a maximum of 2 SOQL queries.
*
* @param ISearchConfiguration The configuration defining the additional fields that should be returned.
* @param ISearchCriteria The criteria defining the records that should be returned.
* @param SearchWindow Defines the subset of the full result set that should be returned.
* @param SearchOrderBy The order in which the records should be returned (is applied prior to the window).
* @return SearchResults The resulting total record count and result set.
*/
public SearchResults selectBySearchCriteria( ISearchConfiguration searchConfiguration, ISearchCriteria criteria, SearchWindow window, SearchOrderBy orderBy )
{
Contract.requires( searchConfiguration != null, 'selectBySearchCriteria called with a null searchConfiguration' );
Contract.requires( criteria != null, 'selectBySearchCriteria called with a null criteria' );
Contract.requires( window != null, 'selectBySearchCriteria called with a null window' );
Contract.requires( orderBy != null, 'selectBySearchCriteria called with a null orderBy' );

Integer countOfRecords = getCountOfRecords( criteria );

List<Sobject> resultsRecords = new List<Sobject>();

if ( countOfRecords > 0 )
{
fflib_QueryFactory queryFactory = newQueryFactory().setCondition( criteria.toSOQL() );

queryFactory.selectFields( searchConfiguration.getRequiredFields() );
queryFactory.setOffset( window.offset );
queryFactory.setLimit( window.length );

if ( orderBy.isConfigured() )
{
queryFactory.setOrdering( orderBy.fieldName , orderBy.direction == 'desc' ? fflib_QueryFactory.SortOrder.DESCENDING : fflib_QueryFactory.SortOrder.ASCENDING );
}

resultsRecords = Database.query( queryFactory.toSOQL() );
}

return new SearchResults( countOfRecords, resultsRecords );
}

/**
* Given a set of search criteria, will return the number of records that meet that criteria
*
* Will perform a maximum of 1 SOQL query
*
* @param ISearchCriteria The criteria defining the records that should be returned.
* @return Integer The total number of records that match the criteria
*/
protected Integer getCountOfRecords( ISearchCriteria criteria )
{
Contract.requires( criteria != null, 'getCountOfRecords called with a null criteria' );

String whereClause = criteria.toSOQL();

if ( String.isBlank( whereClause ) )
{
throw new UnboundCountQueryException( 'Attempted to perform the count of an unbound query against ' + getSObjectName() )
.setErrorCode( FrameworkErrorCodes.SELECTOR_UNBOUND_COUNT_QUERY )
.addContext( 'SObjectType', getSObjectName() );
}

if ( isEnforcingCRUD() && ! SobjectUtils.isAccessible( sObjectType() ) )
{
throw new fflib_SObjectDomain.DomainException( 'Permission to access ' + getSObjectName() + ' denied.' ); // to match the exception thrown by fflib
}

String query = 'SELECT COUNT(Id) recordCount FROM ' + getSObjectName() + ' WHERE ' + whereClause;

AggregateResult result = (AggregateResult)Database.query( query ); // NOPMD: variables come from a trusted source

return (Integer)result.get( 'recordCount' );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@isTest
private without sharing class ortoo_CriteriaTest
{
@isTest
private static void likeString_field_whenCalled_addsALikeToTheGeneratedSoql() // NOPMD: Test method name format
{
ortoo_Criteria criteria = new ortoo_Criteria();

Test.startTest();
criteria.likeString( Contact.Name, 'thing%' );
Test.stopTest();

String expected = 'Name LIKE \'thing%\'';
String got = criteria.toSOQL();

System.assertEquals( expected, got, 'likeString, when called with a field, will add a LIKE to the Generated SOQL' );
}

@isTest
private static void likeString_stringName_whenCalled_addsALikeToTheGeneratedSoql() // NOPMD: Test method name format
{
ortoo_Criteria criteria = new ortoo_Criteria();

Test.startTest();
criteria.likeString( 'Account.Name', 'thing%' );
Test.stopTest();

String expected = 'Account.Name LIKE \'thing%\'';
String got = criteria.toSOQL();

System.assertEquals( expected, got, 'likeString, when called with a string name for a field will add a LIKE to the Generated SOQL' );
}
}
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>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit 0745ef4

Please sign in to comment.