Skip to content

Instantly share code, notes, and snippets.

View aidan-harding's full-sized avatar

Aidan Harding aidan-harding

  • Nebula Consulting
View GitHub Profile
@aidan-harding
aidan-harding / MultiMap.cls
Created August 30, 2024 15:22
A MultiMap for Salesforce Apex
/**
* @author aidan@processity.ai
* @date 29/08/2024
* @description A map where the items are lists of items and you can provide a function for how values map to keys.
* Oh, if we had generics....
*/
public class MultiMap {
private Map<Object, List<Object>> theMap;
private Type listType;
@aidan-harding
aidan-harding / EntityDefinitions.apex
Created April 5, 2024 12:31
Check Salesforce object and field access by querying EntityDefinition and EntityParticle
// Note that WITH USER_MODE is not allowed in Anonymous Apex!
List<EntityDefinition> entityDefinitions = [
SELECT QualifiedApiName
FROM EntityDefinition
WITH USER_MODE
];
// Now entityDefinitions is a list of objects accessible by the current user,
// a bit like Schema.getGlobalDescribe().keySet() but faster!
@aidan-harding
aidan-harding / gist:8ca36333e86fd6ec1a8502b1fd767035
Created August 23, 2023 14:19
Install SFDX project dependencies using jq and the CLI
jq '.packageDirectories[] | select(.default == true) | .dependencies[].package' sfdx-project.json | xargs -p -I {} zsh -c "sf package install --publish-wait 20 --wait 20 --no-prompt --package '{}'"
@aidan-harding
aidan-harding / LazyConcatTest.cls
Last active August 23, 2023 12:49
Probably a misuse of Nebula Core functional stuff, but interesting to try
/**
* @author aidan@nebulaconsulting.co.uk
* @date 22/08/2023
* @description For a given Long text field, when it changes append the old value to the new value with a new line (\n).
* If the field changes to blank/null then the old value remains.
*
* Example:
*
* Old Value New Value Expected Value
* --------------------------------------------
@aidan-harding
aidan-harding / InsertAccountsQueueable.cls
Created July 11, 2023 15:27
Using Queueable Transaction Finalizer to avoid Dev Org Stack Depth limitation
public with sharing class InsertAccountsQueueable implements Queueable, Finalizer {
private Integer nAccounts;
private String name;
public InsertAccountsQueueable(Integer nAccounts, String name) {
this.nAccounts = nAccounts;
this.name = name;
}
@aidan-harding
aidan-harding / ClientCredentialsAuthProvider.cls
Created August 15, 2022 09:23
Auth. Provider for Client Credentials OAuth flow in Salesforce
/**
* @author aidan@nebulaconsulting.co.uk
* @date 21/10/2020
* @description For implementing the Client Credentials OAuth Flow using Named Credentials to store the client id
* and client secret. The initiate method is a dummy one, going straight to the callback, which does a callout to the
* token endpoint. Refreshing is also a dummy, simply obtaining a new token with the original client id and secret.
*/
public with sharing class ClientCredentialsAuthProvider extends Auth.AuthProviderPluginClass {
@aidan-harding
aidan-harding / CovariantReturnTypes.cls
Created July 22, 2022 08:13
Generics could mitigate the lack of covariant return types
public class CovariantReturnTypes {
virtual class X {
virtual Object foo() { return 0; }
}
// I want to do this, but the compiler says "Method return types clash: String vs Object", see
// https://ideas.salesforce.com/s/idea/a0B8W00000GdXOCUA3/support-covariant-return-types-in-apex
class Y extends X {
override String foo() { return ''; }
@aidan-harding
aidan-harding / IsYearChanged.cls
Created July 22, 2022 06:58
Lambda-like class with lots of casting
private class IsYearChanged extends nebc.TriggerContextBooleanFunction {
public override Boolean isTrueFor(SObject oldRecord, SObject newRecord) {
Term__c oldTerm = (Term__c)oldRecord;
Term__c newTerm = (Term__c)newRecord;
return Math.abs(oldTerm.Start_Date__c.year() - newTerm.Start_Date__c.year()) > 0;
}
}
@aidan-harding
aidan-harding / LockForDelete.md
Last active January 12, 2022 10:30
Salesforce locks for deleting?

I have a scenario where an external system is calling Salesforce API, causing a trigger to run. That trigger needs to delete EmailMessage records where the RelatedTo field is an Account.

We're getting UNABLE_TO_LOCK_ROW errors when the external system's API calls overlap and the trigger attempts to delete multiple emails on the same Account at the same time.

This is totally understandable based on how [record-locking works][1]. RelatedTo is either a required lookup or a Master-Detail so deleting the EmailMessage would require a lock on the related Account.

I tried to solve the problem by adding FOR UPDATE to the query in the trigger where it fetches the EmailMessage records.

But, we still observe UNABLE_TO_LOCK_ROW as a DmlException (if it were a QueryException, then that might be due to the 10-second limit timing out).

@aidan-harding
aidan-harding / TransformationTest.cls
Created November 16, 2021 15:57
Tests for a CMDT-driven Apex object transformation library
/**
* @author aidan@nebulaconsulting.co.uk
* @date 15/11/2021
*/
@IsTest
private class TransformationTest {
@IsTest
static void passThroughSObjectToOtherSObject() {