diff --git a/.codeclimate.yml b/.codeclimate.yml index 24965ef..a2ba43b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,7 +1,10 @@ engines: apexmetrics: - enabled: true + enabled: true + config: + rulesets: "./tools/codeclimate/apex/apexunit.xml,./tools/codeclimate/apex/complexity.xml,./tools/codeclimate/apex/performance.xml,./tools/codeclimate/apex/security.xml,./tools/codeclimate/apex/style.xml" + enabled: true ratings: paths: - - "src/**.cls" - - "src/**.trigger" + - "**.cls" + - "**.trigger" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..931beae --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +# Set the default line return behavior, in case people don't have core.autocrlf set. +* text=auto eol=lf + +# Common git file types +.gitattributes text eol=lf +.gitignore text eol=lf +*.md text eol=lf + +# Salesforce-specfic file types +*.app text eol=lf +*.cls text eol=lf +*.cmp text eol=lf +*.component text eol=lf +*.css text eol=lf +*.html text eol=lf +*.js text eol=lf +*.page text eol=lf +*.trigger text eol=lf +*.xml text eol=lf \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b2a7a28..22f74c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ language: node_js - node_js: - "7" - -script: +install: - npm install -g jsforce-metadata-tools - - jsforce-deploy --dry-run -u $DEPLOYMENT_USERNAME -p $DEPLOYMENT_PASSWORD$DEPLOYMENT_TOKEN -D $TRAVIS_BUILD_DIR/src -l $DEPLOYMENT_LOGIN_URL --rollbackOnError true --testLevel $DEPLOYMENT_TEST_LEVEL --pollTimeout $POLL_TIMEOUT \ No newline at end of file +script: + - jsforce-deploy --checkOnly -u $DEPLOYMENT_USERNAME -p $DEPLOYMENT_PASSWORD$DEPLOYMENT_TOKEN -D $TRAVIS_BUILD_DIR/src -l $DEPLOYMENT_LOGIN_URL --rollbackOnError true --testLevel $DEPLOYMENT_TEST_LEVEL --pollTimeout $POLL_TIMEOUT --pollInterval $POLL_INTERVAL--verbose \ No newline at end of file diff --git a/CNAME b/CNAME deleted file mode 100644 index 50fcbda..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -nebulaframework.com \ No newline at end of file diff --git a/README.md b/README.md index 36b2938..7053ff4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,46 @@ # Nebula Framework for Salesforce Apex - - Deploy to Salesforce - - -## Branches -| Name | Build Status | -| -------- | -------- | -| master | | -| dev | | +[![Deploy to Salesforce](https://img.shields.io/badge/salesforce-deploy-blue.svg)](https://githubsfdeploy.herokuapp.com) +[![License: MIT](https://img.shields.io/badge/license-MIT-d742f4.svg)](https://opensource.org/licenses/MIT) +[![Travis CI](https://img.shields.io/travis/jongpie/NebulaFramework/master.svg)](https://travis-ci.org/jongpie/NebulaFramework) +[![Code Climate](https://img.shields.io/codeclimate/github/jongpie/NebulaFramework.svg)](https://codeclimate.com/github/jongpie/NebulaFramework) + +Nebula is a development framework for Salesforce's Apex language on the Force.com platform. It aims to... +1. Provide a foundation for Apex development, with the flexibility to be easily adapted to meet your implementation needs +2. Promote the design of scalable, bulkified code +3. Standardise how your code is written & organised +4. Overcome some gaps in Apex and the Force.com platform + +## Features +Nebula focusses on streamlining how you work with SObjects +1. **SObjectRepository.cls** - this module handles all DML actions & querying needs for an SObject, making the implementation of SObjects much easier & faster + * **QueryBuilder.cls** powers Nebula's querying, allowing you to dynamically build reusable SOQL & SOSL queries + * **SObjectRepositoryMock.cls** can be used in unit tests for test-driven development (TDD) & to drastically reduce the time of your unit tests. +2. **SObjectTriggerHandler.cls** - this module provides a trigger framework to handle all trigger contexts provided by Salesforce, along with additional features like recursion prevention + +The framework also provides several additional classes to make development easier +1. **SObjectRecordTypes.cls** - record types are an important feature of the Force.com platform. Unfortunately, Apex has limitations with handling them - record types have a field called DeveloperName that (you guessed it!) should be used by developers... but native Apex describe methods cannot access this field. Nebula tries to overcome this by providing cacheable query results of record types so you can access the DeveloperName. +2. **Logger.cls** - a flexible logging solution for Apex, leveraged by the framework itself +3. **Environment.cls** - provides information about the current Salesforce environment +4. **UUID.cls** - used to reate a randomly-generated unique ID in your code, using the Universally Unique Identifier (UUID) standard + +## Usage +Nebula uses interfaces, virtual & abstract classes and some Salesforce features (like custom settings) to provide a baseline for your own Apex development. You can deploy the latest version of Nebula to your org and build your implementation on top of it. If you want to customise how Nebula works, most classes & methods can be overridden with your own logic. Ideally, you should minimise any code changes to Nebula's classes so that you can easily upgrade in the future when new versions of Nebula are released. + +Nebula also leverages custom settings to give you control over how the framework works within your Salesforce environment. There are 4 settings +1. **Logger Settings (API Name: NebulaLoggerSettings__c)** + * Enable or disable logging +2. **Record Type Settings (API Name: NebulaRecordTypesSettings__c)** + * Choose how record types are cached + * Select if you want to include managed record types +3. **Repository Settings (API Name: NebulaRepositorySettings__c)** + * Automatically include common fields in your queries, like record ID, audit fields (CreatedById, CreatedDate, etc), Name field (or Subject field, where applicable) and more +4. **Trigger Handler Settings (API Name: NebulaTriggerHandlerSettings__c)** + * Easily disable all triggers & handlers (great for data migration and other admin tasks), + * Enable or disable recursion prevention + +## Versioning +Releases are versioned using [Semantic Versioning](http://semver.org/) in the format 'v1.0.2' (MAJOR.MINOR.PATCH): + +- MAJOR version when incompatible API changes are made +- MINOR version new functionality is added in a backwards-compatible manner +- PATCH version when backwards-compatible bug fixes are made \ No newline at end of file diff --git a/_config.yml b/_config.yml deleted file mode 100644 index c419263..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-cayman \ No newline at end of file diff --git a/src/classes/CollectionUtils.cls b/src/classes/CollectionUtils.cls new file mode 100644 index 0000000..3ad823b --- /dev/null +++ b/src/classes/CollectionUtils.cls @@ -0,0 +1,45 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public without sharing class CollectionUtils { + + public static Boolean isCollection(Object input) { + return isList(input) || isSet(input) || isMap(input); + } + + public static Boolean isList(Object input) { + // If we can cast the object to a list of objects, then it's a list + try { + Object convertedInput = (List)input; + return true; + } catch(System.TypeException ex) { + return false; + } + } + + public static Boolean isSet(Object input) { + // We can't cast the object to a set of objects + // But if we try to cast it to a list of objects & it's a set, + // then a TypeException is thrown so we know it's a set + try { + Object convertedInput = (List)input; + return false; + } catch(System.TypeException ex) { + return ex.getMessage().contains('Set<'); + } + } + + public static Boolean isMap(Object input) { + // We can't cast the object to a map of objects + // But if we try to cast it to a list of objects & it's a map, + // then a TypeException is thrown so we know it's a map + try { + Object convertedInput = (List)input; + return false; + } catch(System.TypeException ex) { + return ex.getMessage().contains('Map<'); + } + } + +} \ No newline at end of file diff --git a/src/classes/Exceptions.cls-meta.xml b/src/classes/CollectionUtils.cls-meta.xml similarity index 100% rename from src/classes/Exceptions.cls-meta.xml rename to src/classes/CollectionUtils.cls-meta.xml diff --git a/src/classes/CollectionUtils_Tests.cls b/src/classes/CollectionUtils_Tests.cls new file mode 100644 index 0000000..eac4e59 --- /dev/null +++ b/src/classes/CollectionUtils_Tests.cls @@ -0,0 +1,140 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class CollectionUtils_Tests { + + // Tests for lists + @isTest + static void it_should_say_that_a_list_of_strings_is_a_list_and_a_collection() { + List collectionToCheck = new List{'A', 'B', 'C'}; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isSet(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isMap(collectionToCheck)); + } + + @isTest + static void it_should_say_that_a_list_of_integers_is_a_list_and_a_collection() { + List collectionToCheck = new List{1, 2, 3}; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isSet(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isMap(collectionToCheck)); + } + + @isTest + static void it_should_say_that_a_list_of_users_is_a_list_and_a_collection() { + List collectionToCheck = [SELECT Id FROM User LIMIT 10]; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isSet(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isMap(collectionToCheck)); + } + + // Tests for sets + @isTest + static void it_should_say_that_a_set_of_strings_is_a_set_and_a_collection() { + Set collectionToCheck = new Set{'A', 'B', 'C'}; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isSet(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isMap(collectionToCheck)); + } + + @isTest + static void it_should_say_that_a_set_of_integers_is_a_set_and_a_collection() { + Set collectionToCheck = new Set{1, 2, 3}; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isSet(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isMap(collectionToCheck)); + } + + @isTest + static void it_should_say_that_a_set_of_users_is_a_set_and_a_collection() { + Set collectionToCheck = new Set([SELECT Id FROM User LIMIT 10]); + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isSet(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isMap(collectionToCheck)); + } + + // Tests for maps + @isTest + static void it_should_say_that_a_map_of_strings_is_a_map_and_a_collection() { + Map collectionToCheck = new Map{ + 'First' => 1, + 'Second' => 2, + 'Third' => 3 + }; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isMap(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isSet(collectionToCheck)); + } + + @isTest + static void it_should_say_that_a_map_of_integers_is_a_map_and_a_collection() { + Map collectionToCheck = new Map{ + 1 => 'First', + 2 => 'Second', + 3 => 'Third' + }; + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isMap(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isSet(collectionToCheck)); + } + + @isTest + static void it_should_say_that_a_map_of_users_is_a_map_and_a_collection() { + Map collectionToCheck = new Map([SELECT Id FROM User LIMIT 10]); + + System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck)); + System.assertEquals(true, CollectionUtils.isMap(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isList(collectionToCheck)); + System.assertEquals(false, CollectionUtils.isSet(collectionToCheck)); + } + + // Negative tests + @isTest + static void it_should_say_that_a_string_is_not_collection() { + String valueToCheck = 'test string'; + + System.assertEquals(false, CollectionUtils.isCollection(valueToCheck)); + System.assertEquals(false, CollectionUtils.isList(valueToCheck)); + System.assertEquals(false, CollectionUtils.isSet(valueToCheck)); + System.assertEquals(false, CollectionUtils.isMap(valueToCheck)); + } + + @isTest + static void it_should_say_that_an_integer_is_not_collection() { + Integer valueToCheck = 1; + + System.assertEquals(false, CollectionUtils.isCollection(valueToCheck)); + System.assertEquals(false, CollectionUtils.isList(valueToCheck)); + System.assertEquals(false, CollectionUtils.isSet(valueToCheck)); + System.assertEquals(false, CollectionUtils.isMap(valueToCheck)); + } + + @isTest + static void it_should_say_that_a_user_is_not_collection() { + User valueToCheck = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()]; + + System.assertEquals(false, CollectionUtils.isCollection(valueToCheck)); + System.assertEquals(false, CollectionUtils.isList(valueToCheck)); + System.assertEquals(false, CollectionUtils.isSet(valueToCheck)); + System.assertEquals(false, CollectionUtils.isMap(valueToCheck)); + } + +} \ No newline at end of file diff --git a/src/classes/CollectionUtils_Tests.cls-meta.xml b/src/classes/CollectionUtils_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/CollectionUtils_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/Environment.cls b/src/classes/Environment.cls index c0044a8..15efef5 100644 --- a/src/classes/Environment.cls +++ b/src/classes/Environment.cls @@ -1,40 +1,42 @@ -/************************************************************************************************* -* This file is part of the Nebula Framework project, released under the MIT License. * -* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * -*************************************************************************************************/ -public class Environment { - - public static String BaseUrl { - get {return URL.getSalesforceBaseUrl().toExternalForm();} - private set; - } - - public static String InstanceName { - get {return organization.InstanceName;} - private set; - } - - public static Boolean IsSandbox { - get {return organization.IsSandbox;} - private set; - } - - public static String Name { - get {return organization.Name;} - private set; - } - - public static String Type { - get {return organization.OrganizationType;} - private set; - } - - private static Organization organization { - get { - if(organization == null) organization = [SELECT Id, InstanceName, IsSandbox, Name, OrganizationType FROM Organization]; - return organization; - } - private set; - } - +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public without sharing class Environment { + + private Environment() {} + + public static String BaseUrl { + get {return URL.getSalesforceBaseUrl().toExternalForm();} + private set; + } + + public static String InstanceName { + get {return organization.InstanceName;} + private set; + } + + public static Boolean IsSandbox { + get {return organization.IsSandbox;} + private set; + } + + public static String Name { + get {return organization.Name;} + private set; + } + + public static String Type { + get {return organization.OrganizationType;} + private set; + } + + private static Organization organization { + get { + if(organization == null) organization = [SELECT Id, InstanceName, IsSandbox, Name, OrganizationType FROM Organization]; + return organization; + } + private set; + } + } \ No newline at end of file diff --git a/src/classes/Environment_Tests.cls b/src/classes/Environment_Tests.cls index c745f0e..4eac013 100644 --- a/src/classes/Environment_Tests.cls +++ b/src/classes/Environment_Tests.cls @@ -1,37 +1,37 @@ -/************************************************************************************************* -* This file is part of the Nebula Framework project, released under the MIT License. * -* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * -*************************************************************************************************/ -@isTest -private class Environment_Tests { - - @isTest - static void it_should_return_base_url() { - System.assert(Environment.BaseUrl.endsWithIgnoreCase('.salesforce.com')); - } - - @isTest - static void it_should_return_instance_name() { - Organization org = [SELECT Id, InstanceName FROM Organization]; - System.assertEquals(org.InstanceName, Environment.InstanceName); - } - - @isTest - static void it_should_return_is_sandbox() { - Organization org = [SELECT Id, IsSandbox FROM Organization]; - System.assertEquals(org.IsSandbox, Environment.IsSandbox); - } - - @isTest - static void it_should_return_name() { - Organization org = [SELECT Id, Name FROM Organization]; - System.assertEquals(org.Name, Environment.Name); - } - - @isTest - static void it_should_return_type() { - Organization org = [SELECT Id, OrganizationType FROM Organization]; - System.assertEquals(org.OrganizationType, Environment.Type); - } - +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class Environment_Tests { + + @isTest + static void it_should_return_base_url() { + System.assert(Environment.BaseUrl.endsWithIgnoreCase('.salesforce.com')); + } + + @isTest + static void it_should_return_instance_name() { + Organization org = [SELECT Id, InstanceName FROM Organization]; + System.assertEquals(org.InstanceName, Environment.InstanceName); + } + + @isTest + static void it_should_return_is_sandbox() { + Organization org = [SELECT Id, IsSandbox FROM Organization]; + System.assertEquals(org.IsSandbox, Environment.IsSandbox); + } + + @isTest + static void it_should_return_name() { + Organization org = [SELECT Id, Name FROM Organization]; + System.assertEquals(org.Name, Environment.Name); + } + + @isTest + static void it_should_return_type() { + Organization org = [SELECT Id, OrganizationType FROM Organization]; + System.assertEquals(org.OrganizationType, Environment.Type); + } + } \ No newline at end of file diff --git a/src/classes/Exceptions.cls b/src/classes/Exceptions.cls deleted file mode 100644 index 70b180e..0000000 --- a/src/classes/Exceptions.cls +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************************************************************* -* This file is part of the Nebula Framework project, released under the MIT License. * -* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * -*************************************************************************************************/ -public without sharing class Exceptions { - - public class InvalidOperationException extends Exception {} - public class RecordTypeException extends Exception {} - public class SObjectTriggerHandlerException extends Exception {} - -} \ No newline at end of file diff --git a/src/classes/ISObjectRecordTypes.cls b/src/classes/ISObjectRecordTypes.cls new file mode 100644 index 0000000..87e6c9d --- /dev/null +++ b/src/classes/ISObjectRecordTypes.cls @@ -0,0 +1,6 @@ +public interface ISObjectRecordTypes { + + Map getAllById(); + Map getAllByDeveloperName(); + +} \ No newline at end of file diff --git a/src/classes/ISObjectRecordTypes.cls-meta.xml b/src/classes/ISObjectRecordTypes.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/ISObjectRecordTypes.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/Logger.cls b/src/classes/Logger.cls index 716b71f..f09d149 100644 --- a/src/classes/Logger.cls +++ b/src/classes/Logger.cls @@ -4,6 +4,8 @@ *************************************************************************************************/ public without sharing class Logger { + private Logger() {} + private static Id logId; private static Attachment logAttachment; @testVisible private static List logMessages; diff --git a/src/classes/NebulaCore.cls b/src/classes/NebulaCore.cls index 89591cf..edb9099 100644 --- a/src/classes/NebulaCore.cls +++ b/src/classes/NebulaCore.cls @@ -4,6 +4,8 @@ *************************************************************************************************/ public abstract class NebulaCore implements INebulaCore { + public enum Module { RECORD_TYPES, REPOSITORY, SETTINGS, TRIGGER_HANDLER } + public static final String TRANSACTION_ID; public static String INITIAL_CLASS {get; private set;} @@ -12,8 +14,6 @@ public abstract class NebulaCore implements INebulaCore { System.debug('NebulaCore.TRANSACTION_ID=' + NebulaCore.TRANSACTION_ID); } - public enum Module { RECORD_TYPES, REPOSITORY, SETTINGS, TRIGGER_HANDLER } - protected final Module currentModule; protected NebulaCore() { diff --git a/src/classes/QueryArgumentFormatter.cls b/src/classes/QueryArgumentFormatter.cls index 0efce6a..5015e26 100644 --- a/src/classes/QueryArgumentFormatter.cls +++ b/src/classes/QueryArgumentFormatter.cls @@ -18,7 +18,9 @@ public virtual class QueryArgumentFormatter extends NebulaCore implements IQuery protected virtual String objectToQueryString(Object valueToFormat) { if(valueToFormat == null) return null; - else if(valueToFormat instanceof List) return this.listToQueryString((List)valueToFormat); + else if(CollectionUtils.isList(valueToFormat)) return this.listToQueryString((List)valueToFormat); + else if(CollectionUtils.isSet(valueToFormat)) return this.setToQueryString(valueToFormat); + else if(CollectionUtils.isMap(valueToFormat)) return this.mapToQueryString(valueToFormat); else if(valueToFormat instanceof QueryDateLiteral) { QueryDateLiteral dateLiteral = (QueryDateLiteral)valueToFormat; return dateLiteral.getValue(); @@ -45,13 +47,31 @@ public virtual class QueryArgumentFormatter extends NebulaCore implements IQuery else return String.valueOf(valueToFormat); } - protected virtual String listToQueryString(List valueList) { + private String listToQueryString(List valueList) { List parsedValueList = new List(); for(Object value : valueList) parsedValueList.add(this.objectToQueryString(value)); - return '(' + String.join(parsedValueList, ',') + ')'; + return '(' + String.join(parsedValueList, ', ') + ')'; } - protected virtual String wrapInSingleQuotes(String input) { + private String setToQueryString(Object valueSet) { + String unformattedString = String.valueOf(valueSet).replace('{', '').replace('}', ''); + List parsedValueList = new List(); + for(String collectionItem : unformattedString.split(',')) { + parsedValueList.add(this.objectToQueryString(collectionItem)); + } + + return '(' + String.join(parsedValueList, ', ') + ')'; + } + + private String mapToQueryString(Object valueMap) { + Map m = (Map)JSON.deserializeUntyped(JSON.serialize(valueMap)); + + return this.setToQueryString(m.keySet()); + } + + private String wrapInSingleQuotes(String input) { + input = input.trim(); + if(input.left(1) != '\'') input = '\'' + input; if(input.right(1) != '\'') input = input + '\''; return input; diff --git a/src/classes/QueryArgumentFormatter_Tests.cls b/src/classes/QueryArgumentFormatter_Tests.cls index ec30f23..7471c60 100644 --- a/src/classes/QueryArgumentFormatter_Tests.cls +++ b/src/classes/QueryArgumentFormatter_Tests.cls @@ -20,7 +20,7 @@ private class QueryArgumentFormatter_Tests { @isTest static void it_should_return_query_string_for_list() { List providedValueList = new List{1, 2, 3}; - String expectedString = '(' + String.join(providedValueList, ',') + ')'; + String expectedString = '(1, 2, 3)'; Test.startTest(); String returnedValue = new QueryArgumentFormatter(providedValueList).getValue(); @@ -29,6 +29,32 @@ private class QueryArgumentFormatter_Tests { System.assertEquals(expectedString, returnedValue); } + @isTest + static void it_should_return_query_string_for_set() { + Set providedValueSet = new Set{'A', 'B', 'C'}; + String expectedString = '(\'A\', \'B\', \'C\')'; + + Test.startTest(); + String returnedValue = new QueryArgumentFormatter(providedValueSet).getValue(); + Test.stopTest(); + + System.assertEquals(expectedString, returnedValue); + } + + @isTest + static void it_should_return_query_string_for_map() { + Map providedValueMap = new Map([SELECT Id FROM User LIMIT 10]); + List sortedIdList = new List(providedValueMap.keySet()); + sortedIdList.sort(); + String expectedString = '(\'' + String.join(sortedIdList, '\', \'') + '\')'; + + Test.startTest(); + String returnedValue = new QueryArgumentFormatter(providedValueMap).getValue(); + Test.stopTest(); + + System.assertEquals(expectedString, returnedValue); + } + @isTest static void it_should_return_query_string_for_query_date_literal() { QueryDateLiteral providedValue = QueryDateLiteral.YESTERDAY; diff --git a/src/classes/QueryBuilder.cls b/src/classes/QueryBuilder.cls index edfbbaa..335c7b2 100644 --- a/src/classes/QueryBuilder.cls +++ b/src/classes/QueryBuilder.cls @@ -18,6 +18,10 @@ public class QueryBuilder extends NebulaCore implements IQueryBuilder { private final SObjectType sobjectType; private final Map sobjectTypeFieldMap; + public QueryBuilder(Schema.SObjectType sobjectType) { + this(sobjectType, null, null); + } + public QueryBuilder(Schema.SObjectType sobjectType, Schema.FieldSet fieldSet, List sobjectFieldList) { this.currentModule = NebulaCore.Module.REPOSITORY; @@ -223,7 +227,7 @@ public class QueryBuilder extends NebulaCore implements IQueryBuilder { } private void logEntry(List results) { - String logEntry = 'Query:\n' + this.query + '\n\nResults:\n' + results; + String logEntry = 'Query:\n' + this.query + '\n\nResults:\n' + JSON.serialize(results); Logger.addEntry(this, logEntry); } diff --git a/src/classes/QueryDateLiteral.cls b/src/classes/QueryDateLiteral.cls index cd179ec..ea0ccc3 100644 --- a/src/classes/QueryDateLiteral.cls +++ b/src/classes/QueryDateLiteral.cls @@ -4,18 +4,6 @@ *************************************************************************************************/ public without sharing class QueryDateLiteral { - private final static String LAST_N = 'LAST_N_{0}: {1}'; - private final static String NEXT_N = 'NEXT_N_{0}: {1}'; - private final static String N_DAYS_AGO = 'N_DAYS_AGO: {0}'; - - private final static String DAYS = 'DAYS'; - private final static String WEEKS = 'WEEKS'; - private final static String MONTHS = 'MONTHS'; - private final static String QUARTERS = 'QUARTERS'; - private final static String FISCAL_QUARTERS = 'FISCAL_QUARTERS'; - private final static String YEARS = 'YEARS'; - private final static String FISCAL_YEARS = 'FISCAL_YEARS'; - private String value; public String getValue() { @@ -26,167 +14,103 @@ public without sharing class QueryDateLiteral { this.value = value; } - //Actual constant literals - public static QueryDateLiteral YESTERDAY { - get { return new QueryDateLiteral('YESTERDAY'); } - } - - public static QueryDateLiteral TODAY { - get { return new QueryDateLiteral('TODAY'); } - } - - public static QueryDateLiteral TOMORROW { - get { return new QueryDateLiteral('TOMORROW'); } - } - - public static QueryDateLiteral LAST_WEEK { - get { return new QueryDateLiteral('LAST_WEEK'); } - } - - public static QueryDateLiteral THIS_WEEK { - get { return new QueryDateLiteral('THIS_WEEK'); } - } - - public static QueryDateLiteral NEXT_WEEK { - get { return new QueryDateLiteral('NEXT_WEEK'); } - } - - public static QueryDateLiteral LAST_MONTH { - get { return new QueryDateLiteral('LAST_MONTH'); } - } - - public static QueryDateLiteral THIS_MONTH { - get { return new QueryDateLiteral('THIS_MONTH'); } - } - - public static QueryDateLiteral NEXT_MONTH { - get { return new QueryDateLiteral('NEXT_MONTH'); } - } - - public static QueryDateLiteral LAST_90_DAYS { - get { return new QueryDateLiteral('LAST_90_DAYS'); } - } - - public static QueryDateLiteral NEXT_90_DAYS { - get { return new QueryDateLiteral('NEXT_90_DAYS'); } - } - - public static QueryDateLiteral THIS_QUARTER { - get { return new QueryDateLiteral('THIS_QUARTER'); } - } - - public static QueryDateLiteral THIS_FISCAL_QUARTER { - get { return new QueryDateLiteral('THIS_FISCAL_QUARTER'); } - } - - public static QueryDateLiteral LAST_QUARTER { - get { return new QueryDateLiteral('LAST_QUARTER'); } - } - - public static QueryDateLiteral LAST_FISCAL_QUARTER { - get { return new QueryDateLiteral('LAST_FISCAL_QUARTER'); } - } - - public static QueryDateLiteral NEXT_QUARTER { - get { return new QueryDateLiteral('NEXT_QUARTER'); } - } - - public static QueryDateLiteral NEXT_FISCAL_QUARTER { - get { return new QueryDateLiteral('NEXT_FISCAL_QUARTER'); } - } - - public static QueryDateLiteral THIS_YEAR { - get { return new QueryDateLiteral('THIS_YEAR'); } - } - - public static QueryDateLiteral THIS_FISCAL_YEAR { - get { return new QueryDateLiteral('THIS_FISCAL_YEAR'); } - } - - public static QueryDateLiteral LAST_YEAR { - get { return new QueryDateLiteral('LAST_YEAR'); } - } - - public static QueryDateLiteral LAST_FISCAL_YEAR { - get { return new QueryDateLiteral('LAST_FISCAL_YEAR'); } - } - - public static QueryDateLiteral NEXT_YEAR { - get { return new QueryDateLiteral('NEXT_YEAR'); } - } - - public static QueryDateLiteral NEXT_FISCAL_YEAR { - get { return new QueryDateLiteral('NEXT_FISCAL_YEAR'); } - } - - ////Buildable literals - public static QueryDateLiteral N_DAYS_AGO(Integer num) { + // Actual constant literals + public static final QueryDateLiteral YESTERDAY = new QueryDateLiteral('YESTERDAY'); + public static final QueryDateLiteral TODAY = new QueryDateLiteral('TODAY'); + public static final QueryDateLiteral TOMORROW = new QueryDateLiteral('TOMORROW'); + public static final QueryDateLiteral LAST_WEEK = new QueryDateLiteral('LAST_WEEK'); + public static final QueryDateLiteral THIS_WEEK = new QueryDateLiteral('THIS_WEEK'); + public static final QueryDateLiteral NEXT_WEEK = new QueryDateLiteral('NEXT_WEEK'); + public static final QueryDateLiteral LAST_MONTH = new QueryDateLiteral('LAST_MONTH'); + public static final QueryDateLiteral THIS_MONTH = new QueryDateLiteral('THIS_MONTH'); + public static final QueryDateLiteral NEXT_MONTH = new QueryDateLiteral('NEXT_MONTH'); + public static final QueryDateLiteral LAST_90_DAYS = new QueryDateLiteral('LAST_90_DAYS'); + public static final QueryDateLiteral NEXT_90_DAYS = new QueryDateLiteral('NEXT_90_DAYS'); + public static final QueryDateLiteral LAST_QUARTER = new QueryDateLiteral('LAST_QUARTER'); + public static final QueryDateLiteral THIS_QUARTER = new QueryDateLiteral('THIS_QUARTER'); + public static final QueryDateLiteral NEXT_QUARTER = new QueryDateLiteral('NEXT_QUARTER'); + public static final QueryDateLiteral LAST_FISCAL_QUARTER = new QueryDateLiteral('LAST_FISCAL_QUARTER'); + public static final QueryDateLiteral THIS_FISCAL_QUARTER = new QueryDateLiteral('THIS_FISCAL_QUARTER'); + public static final QueryDateLiteral NEXT_FISCAL_QUARTER = new QueryDateLiteral('NEXT_FISCAL_QUARTER'); + public static final QueryDateLiteral LAST_YEAR = new QueryDateLiteral('LAST_YEAR'); + public static final QueryDateLiteral THIS_YEAR = new QueryDateLiteral('THIS_YEAR'); + public static final QueryDateLiteral NEXT_YEAR = new QueryDateLiteral('NEXT_YEAR'); + public static final QueryDateLiteral LAST_FISCAL_YEAR = new QueryDateLiteral('LAST_FISCAL_YEAR'); + public static final QueryDateLiteral THIS_FISCAL_YEAR = new QueryDateLiteral('THIS_FISCAL_YEAR'); + public static final QueryDateLiteral NEXT_FISCAL_YEAR = new QueryDateLiteral('NEXT_FISCAL_YEAR'); + + private static final String LAST_N = 'LAST_N_{0}:{1}'; + private static final String NEXT_N = 'NEXT_N_{0}:{1}'; + private static final String N_DAYS_AGO = 'N_DAYS_AGO:{0}'; + + private static final String DAYS = 'DAYS'; + private static final String WEEKS = 'WEEKS'; + private static final String MONTHS = 'MONTHS'; + private static final String QUARTERS = 'QUARTERS'; + private static final String YEARS = 'YEARS'; + private static final String FISCAL_QUARTERS = 'FISCAL_QUARTERS'; + private static final String FISCAL_YEARS = 'FISCAL_YEARS'; + + // Buildable literals + public static QueryDateLiteral N_DAYS_AGO(Integer num) { String parsedValue = String.format(N_DAYS_AGO, new List{String.valueOf(num)}); return new QueryDateLiteral(parsedValue); } - public static QueryDateLiteral LAST_N_DAYS(Integer num) { - String parsedValue = String.format(LAST_N, new List{DAYS,String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral LAST_N_DAYS(Integer num) { + return buildQueryDateLiteral(LAST_N, DAYS, num); } - public static QueryDateLiteral LAST_N_WEEKS(Integer num) { - String parsedValue = String.format(LAST_N, new List{WEEKS, String.valueof(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral LAST_N_WEEKS(Integer num) { + return buildQueryDateLiteral(LAST_N, WEEKS, num); } - public static QueryDateLiteral LAST_N_MONTHS(Integer num) { - String parsedValue = String.format(LAST_N, new List{MONTHS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral LAST_N_MONTHS(Integer num) { + return buildQueryDateLiteral(LAST_N, MONTHS, num); } - public static QueryDateLiteral LAST_N_QUARTERS(Integer num) { - String parsedValue = String.format(LAST_N, new List{QUARTERS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral LAST_N_QUARTERS(Integer num) { + return buildQueryDateLiteral(LAST_N, QUARTERS, num); } - public static QueryDateLiteral LAST_N_YEARS(Integer num) { - String parsedValue = String.format(LAST_N, new List{YEARS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral LAST_N_YEARS(Integer num) { + return buildQueryDateLiteral(LAST_N, YEARS, num); } - public static QueryDateLiteral LAST_N_FISCAL_YEARS(Integer num) { - String parsedValue = String.format(LAST_N, new List{FISCAL_YEARS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral LAST_N_FISCAL_YEARS(Integer num) { + return buildQueryDateLiteral(LAST_N, FISCAL_YEARS, num); } - public static QueryDateLiteral NEXT_N_DAYS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{DAYS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral NEXT_N_DAYS(Integer num) { + return buildQueryDateLiteral(NEXT_N, DAYS, num); } - public static QueryDateLiteral NEXT_N_WEEKS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{WEEKS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral NEXT_N_WEEKS(Integer num) { + return buildQueryDateLiteral(NEXT_N, WEEKS, num); } - public static QueryDateLiteral NEXT_N_MONTHS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{MONTHS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral NEXT_N_MONTHS(Integer num) { + return buildQueryDateLiteral(NEXT_N, MONTHS, num); } - public static QueryDateLiteral NEXT_N_QUARTERS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{QUARTERS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral NEXT_N_QUARTERS(Integer num) { + return buildQueryDateLiteral(NEXT_N, QUARTERS, num); } - public static QueryDateLiteral NEXT_N_FISCAL_QUARTERS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{FISCAL_QUARTERS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral NEXT_N_FISCAL_QUARTERS(Integer num) { + return buildQueryDateLiteral(NEXT_N, FISCAL_QUARTERS, num); } - public static QueryDateLiteral NEXT_N_YEARS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{YEARS, String.valueOf(num)}); - return new QueryDateLiteral(parsedValue); + public static QueryDateLiteral NEXT_N_YEARS(Integer num) { + return buildQueryDateLiteral(NEXT_N, YEARS, num); + } + + public static QueryDateLiteral NEXT_N_FISCAL_YEARS(Integer num) { + return buildQueryDateLiteral(NEXT_N, FISCAL_YEARS, num); } - public static QueryDateLiteral NEXT_N_FISCAL_YEARS(Integer num) { - String parsedValue = String.format(NEXT_N, new List{FISCAL_YEARS, String.valueOf(num)}); + private static QueryDateLiteral buildQueryDateLiteral(String base, String period, Integer num) { + String parsedValue = String.format(base, new List{period, String.valueOf(num)}); return new QueryDateLiteral(parsedValue); } diff --git a/src/classes/QueryDateLiteral_Tests.cls b/src/classes/QueryDateLiteral_Tests.cls index 244db7e..a98420a 100644 --- a/src/classes/QueryDateLiteral_Tests.cls +++ b/src/classes/QueryDateLiteral_Tests.cls @@ -5,7 +5,7 @@ @isTest public class QueryDateLiteral_Tests { - private static Integer n_number = 5; + private static Integer offsetNumber = 5; @isTest static void it_should_return_yesterday_string() { @@ -147,86 +147,86 @@ public class QueryDateLiteral_Tests { @isTest static void it_should_return_n_days_ago_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.N_DAYS_AGO(n_number); - System.assertEquals('N_DAYS_AGO: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.N_DAYS_AGO(offsetNumber); + System.assertEquals('N_DAYS_AGO:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_days_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_DAYS(n_number); - System.assertEquals('LAST_N_DAYS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_DAYS(offsetNumber); + System.assertEquals('LAST_N_DAYS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_weeks_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_WEEKS(n_number); - System.assertEquals('LAST_N_WEEKS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_WEEKS(offsetNumber); + System.assertEquals('LAST_N_WEEKS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_months_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_MONTHS(n_number); - System.assertEquals('LAST_N_MONTHS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_MONTHS(offsetNumber); + System.assertEquals('LAST_N_MONTHS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_quarters_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_QUARTERS(n_number); - System.assertEquals('LAST_N_QUARTERS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_QUARTERS(offsetNumber); + System.assertEquals('LAST_N_QUARTERS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_years_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_YEARS(n_number); - System.assertEquals('LAST_N_YEARS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_YEARS(offsetNumber); + System.assertEquals('LAST_N_YEARS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_fiscal_years_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_FISCAL_YEARS(n_number); - System.assertEquals('LAST_N_FISCAL_YEARS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_FISCAL_YEARS(offsetNumber); + System.assertEquals('LAST_N_FISCAL_YEARS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_days_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_DAYS(n_number); - System.assertEquals('NEXT_N_DAYS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_DAYS(offsetNumber); + System.assertEquals('NEXT_N_DAYS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_weeks_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_WEEKS(n_number); - System.assertEquals('NEXT_N_WEEKS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_WEEKS(offsetNumber); + System.assertEquals('NEXT_N_WEEKS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_months_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_MONTHS(n_number); - System.assertEquals('NEXT_N_MONTHS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_MONTHS(offsetNumber); + System.assertEquals('NEXT_N_MONTHS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_quarters_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_QUARTERS(n_number); - System.assertEquals('NEXT_N_QUARTERS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_QUARTERS(offsetNumber); + System.assertEquals('NEXT_N_QUARTERS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_fiscal_quarters_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_QUARTERS(n_number); - System.assertEquals('NEXT_N_FISCAL_QUARTERS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_QUARTERS(offsetNumber); + System.assertEquals('NEXT_N_FISCAL_QUARTERS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_years_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_YEARS(n_number); - System.assertEquals('NEXT_N_YEARS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_YEARS(offsetNumber); + System.assertEquals('NEXT_N_YEARS:' + offsetNumber, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_fiscal_years_string() { - QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_YEARS(n_number); - System.assertEquals('NEXT_N_FISCAL_YEARS: ' + n_number, dateLiteral.getValue()); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_YEARS(offsetNumber); + System.assertEquals('NEXT_N_FISCAL_YEARS:' + offsetNumber, dateLiteral.getValue()); } } \ No newline at end of file diff --git a/src/classes/QueryFilterScope.cls b/src/classes/QueryFilterScope.cls index 9561280..6e0acc1 100644 --- a/src/classes/QueryFilterScope.cls +++ b/src/classes/QueryFilterScope.cls @@ -2,4 +2,4 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public enum QueryFilterScope {EVERYTHING, TEAM, MINE} \ No newline at end of file +public enum QueryFilterScope { EVERYTHING, TEAM, MINE } \ No newline at end of file diff --git a/src/classes/QueryNullSortOrder.cls b/src/classes/QueryNullSortOrder.cls index aee7a7e..370fa08 100644 --- a/src/classes/QueryNullSortOrder.cls +++ b/src/classes/QueryNullSortOrder.cls @@ -2,4 +2,4 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public enum QueryNullSortOrder {FIRST, LAST} \ No newline at end of file +public enum QueryNullSortOrder { FIRST, LAST } \ No newline at end of file diff --git a/src/classes/QueryOperator.cls b/src/classes/QueryOperator.cls index 12e5101..c55a463 100644 --- a/src/classes/QueryOperator.cls +++ b/src/classes/QueryOperator.cls @@ -14,52 +14,17 @@ public without sharing class QueryOperator { return this.value; } - public static QueryOperator EQUALS { - get {return new QueryOperator('=');} - } - - public static QueryOperator NOT_EQUAL_TO { - get {return new QueryOperator('!=');} - } - - public static QueryOperator GREATER_THAN { - get {return new QueryOperator('>');} - } - - public static QueryOperator GREATER_THAN_OR_EQUAL_TO { - get {return new QueryOperator('>=');} - } - - public static QueryOperator LESS_THAN { - get {return new QueryOperator('<');} - } - - public static QueryOperator LESS_THAN_OR_EQUAL_TO { - get {return new QueryOperator('<=');} - } - - public static QueryOperator IS_IN { - get {return new QueryOperator('IN');} - } - - public static QueryOperator IS_NOT_IN { - get {return new QueryOperator('NOT IN');} - } - - public static QueryOperator INCLUDES { - get {return new QueryOperator('INCLUDES');} - } - - public static QueryOperator EXCLUDES { - get {return new QueryOperator('EXCLUDES');} - } - - public static QueryOperator IS_LIKE { - get {return new QueryOperator('LIKE');} - } - - public static QueryOperator IS_NOT_LIKE { - get {return new QueryOperator('NOT LIKE');} - } + public static final QueryOperator EQUALS = new QueryOperator('='); + public static final QueryOperator NOT_EQUAL_TO = new QueryOperator('!='); + public static final QueryOperator GREATER_THAN = new QueryOperator('>'); + public static final QueryOperator GREATER_THAN_OR_EQUAL_TO = new QueryOperator('>='); + public static final QueryOperator LESS_THAN = new QueryOperator('<'); + public static final QueryOperator LESS_THAN_OR_EQUAL_TO = new QueryOperator('<='); + public static final QueryOperator IS_IN = new QueryOperator('IN'); + public static final QueryOperator IS_NOT_IN = new QueryOperator('NOT IN'); + public static final QueryOperator INCLUDES = new QueryOperator('INCLUDES'); + public static final QueryOperator EXCLUDES = new QueryOperator('EXCLUDES'); + public static final QueryOperator IS_LIKE = new QueryOperator('LIKE'); + public static final QueryOperator IS_NOT_LIKE = new QueryOperator('NOT LIKE'); } \ No newline at end of file diff --git a/src/classes/QuerySearchGroup.cls b/src/classes/QuerySearchGroup.cls index 6c3d4d5..8025b37 100644 --- a/src/classes/QuerySearchGroup.cls +++ b/src/classes/QuerySearchGroup.cls @@ -2,4 +2,4 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public enum QuerySearchGroup {ALL_FIELDS, NAME_FIELDS, EMAIL_FIELDS, PHONE_FIELDS, SIDEBAR_FIELDS} \ No newline at end of file +public enum QuerySearchGroup { ALL_FIELDS, NAME_FIELDS, EMAIL_FIELDS, PHONE_FIELDS, SIDEBAR_FIELDS } \ No newline at end of file diff --git a/src/classes/QuerySortOrder.cls b/src/classes/QuerySortOrder.cls index 0a4f61f..b3daef5 100644 --- a/src/classes/QuerySortOrder.cls +++ b/src/classes/QuerySortOrder.cls @@ -2,4 +2,4 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public enum QuerySortOrder {ASCENDING, DESCENDING} \ No newline at end of file +public enum QuerySortOrder { ASCENDING, DESCENDING } \ No newline at end of file diff --git a/src/classes/SObjectRecordTypes.cls b/src/classes/SObjectRecordTypes.cls index cfbe80c..0077583 100644 --- a/src/classes/SObjectRecordTypes.cls +++ b/src/classes/SObjectRecordTypes.cls @@ -2,13 +2,10 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public abstract class SObjectRecordTypes extends NebulaCore { +public abstract class SObjectRecordTypes extends NebulaCore implements ISObjectRecordTypes { private static Map> cachedRecordTypesBySObjectMap = new Map>(); - public final Map ALL_RECORD_TYPES_BY_ID; - public final Map ALL_RECORD_TYPES_BY_DEVELOPER_NAME; - private Schema.SObjectType sobjectType; private String sobjectName; @@ -21,9 +18,21 @@ public abstract class SObjectRecordTypes extends NebulaCore { Logger.addEntry(this, 'Getting record types for ' + this.sobjectName); this.populateCache(); + } + + public Map getAllById() { + return new Map(cachedRecordTypesBySObjectMap.get(this.sobjectName)); + } + + public Map getAllByDeveloperName() { + Map allRecordTypesByDeveloperName = new Map(); + for(RecordType recordType : this.getAllById().values()) { + if(recordType.SObjectType != this.sobjectName) continue; + + allRecordTypesByDeveloperName.put(recordType.DeveloperName, recordType); + } - this.ALL_RECORD_TYPES_BY_ID = this.getAllRecordTypesById(); - this.ALL_RECORD_TYPES_BY_DEVELOPER_NAME = this.getAllRecordTypesByDeveloperName(); + return allRecordTypesByDeveloperName; } // TODO clean up all this mess for cache, getRecordTypes, getAllRecordTypesByDeveloperName, etc @@ -32,11 +41,9 @@ public abstract class SObjectRecordTypes extends NebulaCore { if(cachedRecordTypesBySObjectMap.containsKey(this.sobjectName)) return; Schema.SObjectType recordTypeSObjectType = Schema.getGlobalDescribe().get('RecordType'); - Schema.FieldSet nullFieldSet; - List nullSObjectFieldList; // If we don't have the SObject cached, then we need to query - QueryBuilder query = new QueryBuilder(recordTypeSObjectType, nullFieldSet, nullSObjectFieldList); + QueryBuilder query = new QueryBuilder(recordTypeSObjectType); if(NebulaSettings.RecordTypesSettings.LazyLoad__c) { query.filterBy(new QueryFilter(Schema.RecordType.SObjectType, QueryOperator.EQUALS, this.sobjectName)); @@ -44,7 +51,7 @@ public abstract class SObjectRecordTypes extends NebulaCore { Logger.addEntry(this, 'this.sobjectName=' + this.sobjectName); } - if(NebulaSettings.RecordTypesSettings.ExcludeManagedRecordTypes__c) { + if(!NebulaSettings.RecordTypesSettings.IncludeManagedRecordTypes__c) { query.filterBy(new QueryFilter(Schema.RecordType.NamespacePrefix, QueryOperator.EQUALS, null)); } @@ -59,19 +66,4 @@ public abstract class SObjectRecordTypes extends NebulaCore { Logger.addEntry(this, 'cachedRecordTypesBySObjectMap=' + cachedRecordTypesBySObjectMap); } - private Map getAllRecordTypesById() { - return new Map(cachedRecordTypesBySObjectMap.get(this.sobjectName)); - } - - private Map getAllRecordTypesByDeveloperName() { - Map allRecordTypesByDeveloperName = new Map(); - for(RecordType recordType : this.getAllRecordTypesById().values()) { - if(recordType.SObjectType != this.sobjectName) continue; - - allRecordTypesByDeveloperName.put(recordType.DeveloperName, recordType); - } - - return allRecordTypesByDeveloperName; - } - } \ No newline at end of file diff --git a/src/classes/SObjectRecordTypes_Tests.cls b/src/classes/SObjectRecordTypes_Tests.cls index a077f60..1d76c93 100644 --- a/src/classes/SObjectRecordTypes_Tests.cls +++ b/src/classes/SObjectRecordTypes_Tests.cls @@ -18,9 +18,9 @@ private class SObjectRecordTypes_Tests { Test.startTest(); - System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.size()); + System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById().size()); for(RecordType recordType : expectedRecordTypeList) { - System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.containsKey(recordType.Id)); + System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById().containsKey(recordType.Id)); } Test.stopTest(); @@ -32,9 +32,9 @@ private class SObjectRecordTypes_Tests { Test.startTest(); - System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_DEVELOPER_NAME.size()); + System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().getAllByDeveloperName().size()); for(RecordType recordType : expectedRecordTypeList) { - System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_DEVELOPER_NAME.containsKey(recordType.DeveloperName)); + System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().getAllByDeveloperName().containsKey(recordType.DeveloperName)); } Test.stopTest(); @@ -46,16 +46,16 @@ private class SObjectRecordTypes_Tests { List expectedRecordTypeList = [SELECT Id, DeveloperName, NamespacePrefix FROM RecordType WHERE SObjectType = 'Lead' AND NamespacePrefix = null]; NebulaRecordTypesSettings__c recordTypesSettings = NebulaRecordTypesSettings__c.getInstance(); - recordTypesSettings.ExcludeManagedRecordTypes__c = true; + recordTypesSettings.IncludeManagedRecordTypes__c = false; upsert recordTypesSettings; Test.startTest(); - System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.size()); + System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById().size()); for(RecordType recordType : expectedRecordTypeList) { - System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.containsKey(recordType.Id)); + System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById().containsKey(recordType.Id)); } - for(RecordType recordType : new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.values()) { + for(RecordType recordType : new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById().values()) { System.assertEquals(null, recordType.NamespacePrefix); } @@ -72,7 +72,7 @@ private class SObjectRecordTypes_Tests { System.assertEquals(0, Limits.getQueries()); for(Integer i = 0; i < 10; i++) { - System.debug(new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID); + System.debug(new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById()); } System.assertEquals(1, Limits.getQueries()); @@ -90,7 +90,7 @@ private class SObjectRecordTypes_Tests { System.assertEquals(0, Limits.getQueries()); for(Integer i = 0; i < 10; i++) { - System.debug(new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID); + System.debug(new SObjectRecordTypes_Tests.LeadRecordTypes().getAllById()); } System.assertEquals(1, Limits.getQueries()); diff --git a/src/classes/SObjectRepository.cls b/src/classes/SObjectRepository.cls index c4f9202..16f3dc4 100644 --- a/src/classes/SObjectRepository.cls +++ b/src/classes/SObjectRepository.cls @@ -2,7 +2,7 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public virtual class SObjectRepository extends DML implements ISObjectRepository, IDML { +public abstract class SObjectRepository extends DML implements ISObjectRepository, IDML { protected IQueryBuilder Query { get { return new QueryBuilder(this.sobjectType, this.fieldSet, this.sobjectFieldList); } diff --git a/src/classes/SObjectRepository_Tests.cls b/src/classes/SObjectRepository_Tests.cls index 96afa07..bba02f4 100644 --- a/src/classes/SObjectRepository_Tests.cls +++ b/src/classes/SObjectRepository_Tests.cls @@ -150,8 +150,7 @@ private class SObjectRepository_Tests { @isTest static void it_should_return_for_ids_in_set_and_query_filters() { Map expectedAccountMap = new Map([SELECT Id FROM Account]); - List accountFields = new List{Schema.Account.Id, Schema.Account.Name}; - ISObjectRepository repo = new SObjectRepository(Schema.Account.SObjectType, accountFields); + ISObjectRepository repo = new SObjectRepository_Tests.AccountRepository(); Test.startTest(); QueryFilter queryFilter = new QueryFilter(Schema.Account.Name, QueryOperator.EQUALS, 'My Test Company'); @@ -168,7 +167,7 @@ private class SObjectRepository_Tests { static void it_should_return_by_field_for_ids_in_list() { Map expectedAccountMap = new Map([SELECT Id FROM Account]); List accountFields = new List{Schema.Account.Id, Schema.Account.Name}; - ISObjectRepository repo = new SObjectRepository(Schema.Account.SObjectType, accountFields); + ISObjectRepository repo = new SObjectRepository_Tests.AccountRepository(); Test.startTest(); QueryFilter queryFilter = new QueryFilter(Schema.Account.Name, QueryOperator.EQUALS, 'My Test Company'); @@ -332,7 +331,7 @@ private class SObjectRepository_Tests { new SObjectRepository_Tests.UserRepository().upsertRecords(existingUser, Schema.User.Username); Test.stopTest(); - existingUser = [SELECT Id, LastModifiedDate FROM User LIMIT 1]; + existingUser = [SELECT Id, LastModifiedDate FROM User WHERE Id = :existingUser.Id]; System.assert(existingUser.LastModifiedDate > originalLastModifiedDate, existingUser); } diff --git a/src/classes/SObjectTriggerHandler.cls b/src/classes/SObjectTriggerHandler.cls index 32a2c4a..34eefde 100644 --- a/src/classes/SObjectTriggerHandler.cls +++ b/src/classes/SObjectTriggerHandler.cls @@ -105,7 +105,7 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje if(this.isTestMode) return; String errorMessage = 'Trigger handler called outside of trigger execution ' + this.isTriggerExecuting; - if(!this.isTriggerExecuting || this.currentTriggerContext == null) throw new Exceptions.SObjectTriggerHandlerException(errorMessage); + if(!this.isTriggerExecuting || this.currentTriggerContext == null) throw new SObjectTriggerHandlerException(errorMessage); } private void setTriggerRecords() { @@ -176,4 +176,7 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje return true; } } + + private class SObjectTriggerHandlerException extends Exception {} + } \ No newline at end of file diff --git a/src/classes/Scheduler.cls b/src/classes/Scheduler.cls new file mode 100644 index 0000000..ee48d48 --- /dev/null +++ b/src/classes/Scheduler.cls @@ -0,0 +1,28 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public class Scheduler { + //CRON order - Seconds, Minutes, Hours, Day_of_month, Month, Day_of_week, Optional_year + public final static String DAILY_CRON = '0 {0} {1} * * ?'; + public final static String HOURLY_CRON = '0 {0} * * * ?'; + private final Schedulable scheduledClass; + + public Scheduler (Schedulable scheduledClass) { + this.scheduledClass = scheduledClass; + } + + public String scheduleHourly(String jobName, String startingMinuteInHour) { + String hourlyCRON = String.format(HOURLY_CRON, new List{startingMinuteInHour}); + return this.schedule(jobName, hourlyCRON); + } + + public String scheduleDaily(String jobName, String startingHour, String startingMinute) { + String dailyCRON = String.format(DAILY_CRON, new List{startingHour, startingMinute}); + return this.schedule(jobName, dailyCRON); + } + + public String schedule(String jobName, String cronExpression) { + return System.schedule(jobName, cronExpression, this.scheduledClass); + } +} \ No newline at end of file diff --git a/src/classes/Scheduler.cls-meta.xml b/src/classes/Scheduler.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/Scheduler.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/Scheduler_Tests.cls b/src/classes/Scheduler_Tests.cls new file mode 100644 index 0000000..96322da --- /dev/null +++ b/src/classes/Scheduler_Tests.cls @@ -0,0 +1,27 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +global class Scheduler_Tests { + private static final String SCHEDULABLE_JOB_ID_STRING = '7'; + + global class TestSchedulable implements Schedulable { + global void execute(SchedulableContext sc) {} + } + + @isTest + static void it_should_successfully_schedule_daily() { + String CRON_EXP = '0 59 23 * * ?'; + String jobId = new Scheduler(new TestSchedulable()).scheduleDaily(SCHEDULABLE_JOB_ID_STRING, '59', '23'); + CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger Where Id = :jobId]; + System.assertEquals(CRON_EXP, ct.CronExpression); + } + + @isTest + static void it_should_successfully_schedule_hourly() { + String jobId = new Scheduler(new TestSchedulable()).scheduleHourly(SCHEDULABLE_JOB_ID_STRING, '59'); + CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger Where Id = :jobId]; + System.assertEquals(String.format(Scheduler.HOURLY_CRON,new List{'59'}), ct.CronExpression); + } +} \ No newline at end of file diff --git a/src/classes/Scheduler_Tests.cls-meta.xml b/src/classes/Scheduler_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/Scheduler_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/UUID.cls b/src/classes/UUID.cls index e4e23f6..63caf3b 100644 --- a/src/classes/UUID.cls +++ b/src/classes/UUID.cls @@ -18,16 +18,33 @@ public without sharing class UUID { return uuidMatcher.matches(); } + private String value; + + public UUID() { + this.setValue(); + } + public String getValue() { + return value; + } + + private void setValue() { Blob generatedBlob = Crypto.GenerateAESKey(128); String hex = EncodingUtil.ConvertTohex(generatedBlob); - String uuid = hex.substring(0, 8) - + '-' + hex.substring(8, 12) - + '-' + hex.substring(12, 16) - + '-' + hex.substring(16, 20) - + '-' + hex.substring(20); + this.value = this.formatValue(hex); + } + + private String formatValue(String unformattedValue) { + // Remove any non-alphanumeric characters. Should be unnecessary, but better to be safe than sorry. + unformattedValue = unformattedValue.replaceAll('[^a-zA-Z0-9]', ''); + + String formattedValue = unformattedValue.substring(0, 8) + + '-' + unformattedValue.substring(8, 12) + + '-' + unformattedValue.substring(12, 16) + + '-' + unformattedValue.substring(16, 20) + + '-' + unformattedValue.substring(20); - return uuid.toUpperCase(); + return formattedValue.toUpperCase(); } } \ No newline at end of file diff --git a/src/classes/UUID_Tests.cls b/src/classes/UUID_Tests.cls index ccba7f3..b31d96e 100644 --- a/src/classes/UUID_Tests.cls +++ b/src/classes/UUID_Tests.cls @@ -16,6 +16,16 @@ private class UUID_Tests { System.assertEquals(36, generatedUUID.length()); } + @isTest + static void it_should_reuse_a_UUID_on_subsequent_calls_to_getValue() { + UUID theUUID = new UUID(); + String originalValue = theUUID.getValue(); + + for(Integer i = 0; i < 5; i++) { + System.assertEquals(originalValue, theUUID.getValue()); + } + } + @isTest static void it_should_verify_that_a_UUID_is_a_UUID() { String generatedUUID = new UUID().getValue(); diff --git a/src/objects/NebulaRecordTypesSettings__c.object b/src/objects/NebulaRecordTypesSettings__c.object index 3654505..67599d9 100644 --- a/src/objects/NebulaRecordTypesSettings__c.object +++ b/src/objects/NebulaRecordTypesSettings__c.object @@ -4,12 +4,12 @@ Controls the behavior of the class SObjectRecordTypes.cls false - ExcludeManagedRecordTypes__c - false - Determines if managed record types are excluded from queries. If you do not use any managed record types, then you should enabled this. + IncludeManagedRecordTypes__c + true + Determines if managed record types are included in queries. If you do not use any managed record types, then you can disable this. false - Determines if managed record types are excluded from queries. If you do not use any managed record types, then you should enabled this. - + Determines if managed record types are included in queries. If you do not use any managed record types, then you can disable this. + false Checkbox diff --git a/tools/codeclimate/apex/apexunit.xml b/tools/codeclimate/apex/apexunit.xml new file mode 100644 index 0000000..779e06d --- /dev/null +++ b/tools/codeclimate/apex/apexunit.xml @@ -0,0 +1,58 @@ + + + + + These rules deal with different problems that can occur with Apex unit tests. + + + + + Apex unit tests should include at least one assertion. This makes the tests more robust, and using assert + with messages provide the developer a clearer idea of what the test does. + + 3 + + + + + + + Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database data for unexpected modification by tests. + + 3 + + + + + diff --git a/tools/codeclimate/apex/complexity.xml b/tools/codeclimate/apex/complexity.xml new file mode 100644 index 0000000..cff67fd --- /dev/null +++ b/tools/codeclimate/apex/complexity.xml @@ -0,0 +1,283 @@ + + + + + +The Complexity ruleset contains rules that find problems related to code size or complexity. + + + + +Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. + + 3 + +y) { + if (y>z) { + if (z==x) { + // !! too deep + } + } + } + } +} +]]> + + + + + +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + + 3 + + + + + + + +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + + 3 + + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + + 3 + + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + + 3 + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + + 3 + + + + + + + + + + 3 + + + + + + + +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. + + 3 + + + + + + + +Classes with large numbers of public methods and attributes require disproportionate testing efforts +since combinational side effects grow rapidly and increase risk. Refactoring these classes into +smaller ones not only increases testability and reliability but also allows new variations to be +developed easily. + + 3 + + + + + + \ No newline at end of file diff --git a/tools/codeclimate/apex/performance.xml b/tools/codeclimate/apex/performance.xml new file mode 100644 index 0000000..3699dfe --- /dev/null +++ b/tools/codeclimate/apex/performance.xml @@ -0,0 +1,54 @@ + + + + +The Performance ruleset contains a collection of good practices which should be followed. + + + + +New objects created within loops should be checked to see if they can created outside them and reused. + + 3 + + accounts = [SELECT Id FROM Account]; + } + } +} +]]> + + + + + Avoid DML statements inside loops to avoid hitting the DML governor limit. Instead, try to batch up the data into a list and invoke your DML once on that list of data outside the loop. + 3 + + + + + + \ No newline at end of file diff --git a/tools/codeclimate/apex/security.xml b/tools/codeclimate/apex/security.xml new file mode 100644 index 0000000..446812d --- /dev/null +++ b/tools/codeclimate/apex/security.xml @@ -0,0 +1,265 @@ + + + + + These rules deal with different security problems that can occur within Apex. + + + + +Detect classes declared without explicit sharing mode if DML methods are used. This +forces the developer to take access restrictions into account before modifying objects. + + 3 + + + + + + + +Checks against redirects to user-controlled locations. This prevents attackers from +redirecting users to phishing sites. + + 3 + + + + + + + + +Checks against accessing endpoints under plain **http**. You should always use +**https** for security. + + 3 + + + + + + + +Makes sure that all values obtained from URL parameters are properly escaped / sanitized +to avoid XSS attacks. + + 3 + + + + + + + + +Reports on calls to `addError` with disabled escaping. The message passed to `addError` +will be displayed directly to the user in the UI, making it prime ground for XSS +attacks if unescaped. + + 3 + + + + + + + +The rule makes sure you are using randomly generated IVs and keys for `Crypto` calls. +Hard-wiring these values greatly compromises the security of encrypted data. + + 3 + + + + + + + + +Check to avoid making DML operations in Apex class constructor/init method. This prevents +modification of the database just by accessing a page. + + 3 + + + + + + + + Detects the usage of untrusted / unescaped variables in DML queries. + + 3 + + + + + + + +The rule validates you are checking for access permissions before a SOQL/SOSL/DML operation. +Since Apex runs in system mode not having proper permissions checks results in escalation of +privilege and may produce runtime errors. This check forces you to handle such scenarios. + + 3 + + + + + + + + 3 + + + + + + + + 3 + + + + + + + diff --git a/tools/codeclimate/apex/style.xml b/tools/codeclimate/apex/style.xml new file mode 100644 index 0000000..5688c58 --- /dev/null +++ b/tools/codeclimate/apex/style.xml @@ -0,0 +1,135 @@ + + + + +The Style Ruleset contains rules regarding preferred usage of names and identifiers. + + + + +A variable naming conventions rule - customize this to your liking. Currently, it +checks for final variables that should be fully capitalized and non-final variables +that should not include underscores. + + 1 + + + + + + + +Method names should always begin with a lower case character, and should not contain underscores. + + 1 + + + + + + + +Class names should always begin with an upper case character. + + 1 + + + + + + + +Non-constructor methods should not have the same name as the enclosing class. + + 3 + + + + + + + +As triggers do not allow methods like regular classes they are less flexible and suited to apply good encapsulation style. +Therefore delegate the triggers work to a regular class (often called Trigger handler class). + +See more here: https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices + + 3 + + + + + + + +Global classes should be avoided (especially in managed packages) as they can never be deleted or changed in signature. Always check twice if something needs to be global. +Many interfaces (e.g. Batch) required global modifiers in the past but don't require this anymore. Don't lock yourself in. + + 3 + + + + + +