Skip to content

fix(QueueableProcess): Updates Queueable Process#345

Merged
codefriar merged 1 commit intomainfrom
fix/testUpdatesAndFixes
Jun 9, 2025
Merged

fix(QueueableProcess): Updates Queueable Process#345
codefriar merged 1 commit intomainfrom
fix/testUpdatesAndFixes

Conversation

@codefriar
Copy link
Owner

@codefriar codefriar commented Jun 8, 2025

Test fixes, and a breaking change to QueueableProcess

Summary by CodeRabbit

  • Refactor: Updated ULID class for improved readability and consistency.
  • New Feature: Enhanced SObjectFactory methods with a new parameter to control fake ID generation, improving test data creation flexibility.
  • New Feature: Added a method in SObjectFactoryHelper to respect name and autonumber rules during object cloning.
  • Refactor: Optimized process step execution in QueueableProcess and EnqueueNextQueueableProcessStep classes for efficient data pass-through.
  • Test: Improved ParameterMatcherTest structure by introducing a separate ParameterMatcherTestClass, enhancing test maintainability.

To Fix a logic, order-of-operations error, and to capture the results of the sub-class' execute method
@github-actions
Copy link

github-actions bot commented Jun 8, 2025

Image description CodeRabbit

Walkthrough

This update brings a series of changes aimed at improving code readability, consistency, and functionality. It includes renaming variables for clarity, updating method signatures, adding new parameters to control behavior, refactoring methods for efficient data handling, and enhancing test classes with better structure and additional functionalities.

Changes

Files Summary
force-app/main/default/classes/ULID/ULID.cls Renamed CHARACTERSET to CHARACTER_SET and adjusted related variables for consistency. Refactored some variable names for clarity and readability.
force-app/main/default/classes/polyfills/tests/PolyfillsTests.cls,
force-app/main/default/classes/test utilities/SObjectFactory.cls,
force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls
Updated the createSObjects method call by adding a new parameter and changing the order of existing parameters. Added a new flag to control fake ID generation behavior. Introduced a new method mutateCloneToRespectNameAndAutonumberRules.
force-app/main/default/classes/queueable process/EnqueueNextQueueableProcessStep.cls,
force-app/main/default/classes/queueable process/QueueableProcess.cls,
force-app/main/default/classes/queueable process/tests/ExampleQueueableProcessSteps.cls
Updated EnqueueNextQueueableProcessStep class to dequeue the first process step, set necessary properties, and enqueue the next process step. Added return type to the execute method in QueueableProcess. Refactored the execute(QueueableContext context) method. Updated execute() methods in ExampleQueueableProcessSteps to return an Object.
force-app/main/default/classes/test utilities/ParameterMatcher.cls,
force-app/main/default/classes/test utilities/ParameterMatcherTest.cls,
force-app/main/default/classes/test utilities/ParameterMatcherTestClass.cls,
force-app/main/default/classes/test utilities/ParameterMatcherTestClass.cls-meta.xml
Removed an unnecessary newline in the ParameterMatcher enum definition. Refactored the ParameterMatcherTest class by replacing the inner TestClass with a new ParameterMatcherTestClass. Added a test class ParameterMatcherTestClass with a method methodWithParameters. Added an Apex class definition with API version 63.0 and status set to Active.

🐰 "In the land of code, where logic is king,
Changes are made, improvements they bring.
Variables renamed, methods refined,
For clarity's sake, and a developer's mind.
Tests are enhanced, with structure anew,
Making sure everything continues to run true.
So here's to the code, ever evolving, never still,
In the quest for perfection, we find our thrill." 🎉


Uplevel your code reviews with CodeRabbit Pro

CodeRabbit Pro

If you like this project, please support us by purchasing the Pro version. The Pro version has advanced context, superior noise reduction and several proprietary improvements compared to the open source version. Moreover, CodeRabbit Pro is free for open source projects.

Comment on lines +6 to +8
public String methodWithParameters(String param1, Integer param2) {
return param1 + String.valueOf(param2);
}

Check warning

Code scanning / PMD

Missing ApexDoc comment Warning test

Missing ApexDoc comment
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

Commits Files that changed from the base of the PR and between 1e34e3f and 15ef805 commits.
Files selected (13)
  • force-app/main/default/classes/ULID/README.md (1)
  • force-app/main/default/classes/ULID/ULID.cls (4)
  • force-app/main/default/classes/polyfills/tests/PolyfillsTests.cls (1)
  • force-app/main/default/classes/queueable process/EnqueueNextQueueableProcessStep.cls (1)
  • force-app/main/default/classes/queueable process/QueueableProcess.cls (2)
  • force-app/main/default/classes/queueable process/README.md (1)
  • force-app/main/default/classes/queueable process/tests/ExampleQueueableProcessSteps.cls (2)
  • force-app/main/default/classes/test utilities/ParameterMatcher.cls (1)
  • force-app/main/default/classes/test utilities/ParameterMatcherTest.cls (3)
  • force-app/main/default/classes/test utilities/ParameterMatcherTestClass.cls (1)
  • force-app/main/default/classes/test utilities/ParameterMatcherTestClass.cls-meta.xml (1)
  • force-app/main/default/classes/test utilities/SObjectFactory.cls (9)
  • force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls (2)
Files not summarized due to errors (2)
  • force-app/main/default/classes/ULID/README.md (diff tokens exceeds limit)
  • force-app/main/default/classes/queueable process/README.md (diff tokens exceeds limit)
Files skipped from review due to trivial changes (4)
  • force-app/main/default/classes/ULID/ULID.cls
  • force-app/main/default/classes/test utilities/ParameterMatcher.cls
  • force-app/main/default/classes/test utilities/ParameterMatcherTest.cls
  • force-app/main/default/classes/test utilities/ParameterMatcherTestClass.cls-meta.xml
Review comments generated (19)
  • Review: 10
  • LGTM: 9

Tips

Chat with Image description CodeRabbit Bot (@coderabbitai)

  • Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
  • Invite the bot into a review comment chain by tagging @coderabbitai in a reply.

Code suggestions

  • The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
  • You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.

Pausing incremental reviews

  • Add @coderabbitai: ignore anywhere in the PR description to pause further reviews from the bot.

Comment on lines +165 to +167
null,
false,
true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The createSObjects method call has been updated with new parameters. Ensure that the new parameters (null, false, true) are correctly passed and they align with the method's expectations. If these parameters have default values, consider using the method overloading principle to avoid passing them explicitly each time.

- SObjectFactory.createSObjects(new Contact(AccountId = acct.Id), 5, false)
+ SObjectFactory.createSObjects(new Contact(AccountId = acct.Id), 5, null, false, true)

Please also ensure that the changes do not break any existing functionality or tests.

Comment on lines +71 to +81
// Remove the first element from the list - this was constructed using the .then() method.
QueueableProcess nextProcessStep = this.processSteps.remove(0);
// Set the remaining steps on the next process step.
nextProcessStep.processSteps = this.processSteps;
// Set the dataPassthrough on the next process step.
nextProcessStep.dataPassthrough = dataPassthrough;
// Set the queueableContextHistory on the next process step.
nextProcessStep.queueableContextHistory = this.queueableContextHistory;
// Set the finalizerContextHistory on the next process step.
nextProcessStep.finalizerContextHistory = this.finalizerContextHistory;
// Enqueue the next process step.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The logic here seems to be correct, but it's not very efficient. You're directly modifying the processSteps list of the current object and assigning it to the nextProcessStep. This means that all subsequent steps will share the same list instance, which could lead to unexpected behavior if any step modifies the list.

Instead, consider creating a new list that excludes the first element and assign this new list to the nextProcessStep. This way, each step has its own copy of the remaining steps, and modifications in one step won't affect others.

- QueueableProcess nextProcessStep = this.processSteps.remove(0);
- nextProcessStep.processSteps = this.processSteps;
+ QueueableProcess nextProcessStep = this.processSteps[0];
+ nextProcessStep.processSteps = this.processSteps.subList(1, this.processSteps.size());

This change also avoids the need to remove an element from the beginning of the list, which can be an expensive operation for large lists.

Comment on lines +43 to +57
* an account and increment the shipping street by 1. Returns the incremented dataPassthrough value.
*
* @return Object - the incremented dataPassthrough value for the next step
*/
public override void execute() {
public override Object execute() {
Account acct = fetchAccountByIdForDemoPurposes(this.accountId);
Integer castedInteger = Integer.valueOf(acct.ShippingStreet);
acct.ShippingStreet = String.valueOf(castedInteger + 1);
Integer nextDataPassthrough = null;
if (this.dataPassthrough != null) {
this.dataPassthrough = 1 + (Integer) this.dataPassthrough;
acct.BillingStreet = String.valueOf(this.dataPassthrough);
nextDataPassthrough = 1 + (Integer) this.dataPassthrough;
acct.BillingStreet = String.valueOf(nextDataPassthrough);
}
Database.update(acct, false, AccessLevel.USER_MODE);
return nextDataPassthrough;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The logic in this method seems to be correct, but there's a potential issue with error handling. If the ShippingStreet field of the Account object is not an integer, the Integer.valueOf(acct.ShippingStreet) will throw a NumberFormatException. It would be better to handle this exception and provide a meaningful error message.

- Integer castedInteger = Integer.valueOf(acct.ShippingStreet);
+ Integer castedInteger;
+ try {
+     castedInteger = Integer.valueOf(acct.ShippingStreet);
+ } catch (NumberFormatException e) {
+     throw new IllegalArgumentException('ShippingStreet must be an integer');
+ }

Comment on lines +103 to +113
* update the account phone field when the data Passthrough isn't null. Returns the same dataPassthrough value.
*
* @return Object - the same dataPassthrough value for the next step
*/
public override void execute() {
public override Object execute() {
Account acct = fetchAccountByIdForDemoPurposes(this.accountId);
if (this.dataPassthrough != null) {
acct.Phone = String.valueOf(this.dataPassthrough);
}
Database.update(acct, false, AccessLevel.USER_MODE);
return this.dataPassthrough;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

Similar to the previous comment, there's a potential issue with error handling. If the dataPassthrough is not an integer, the String.valueOf(this.dataPassthrough) will throw a ClassCastException. It would be better to handle this exception and provide a meaningful error message.

- acct.Phone = String.valueOf(this.dataPassthrough);
+ try {
+     acct.Phone = String.valueOf(this.dataPassthrough);
+ } catch (ClassCastException e) {
+     throw new IllegalArgumentException('dataPassthrough must be an integer');
+ }

Comment on lines +84 to +107
) {
return createSObject(prototype, usingDefaultsClassName, forceInsert, false);
}

/**
* @description Creates an SObject with the given prototype, using defaults from the specified class, optionally inserts it into the database, and optionally prevents fake ID generation.
*
* @param prototype The prototype SObject to be created.
* @param usingDefaultsClassName The name of the class providing default values for the SObject.
* @param forceInsert Indicates whether to insert the SObject into the database.
* @param preventFakeId Indicates whether to prevent fake ID generation when not inserting.
*
* @return The created SObject.
*/
public static SObject createSObject(
SObject prototype,
String usingDefaultsClassName,
Boolean forceInsert,
Boolean preventFakeId
) {
prototype = internalCreateSObject(prototype, usingDefaultsClassName);
if (forceInsert) {
Database.insert(prototype, AccessLevel.SYSTEM_MODE);
} else {
} else if (!preventFakeId) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The changes made here are significant and seem to be well thought out. The addition of the preventFakeId parameter allows for more control over the creation of SObjects, which can be useful in certain scenarios. However, it's important to ensure that all calls to this method have been updated to include this new parameter, otherwise, it could lead to unexpected behavior.

Comment on lines 192 to 219
Integer count,
String usingDefaultsClassName,
Boolean forceInsert
) {
return createSObjects(prototype, count, usingDefaultsClassName, forceInsert, false);
}

/**
* @description Creates a list of SObjects based on a prototype, count, defaults class, force insert flag, and preventFakeId flag.
*
* @param prototype The prototype SObject to clone.
* @param count The number of SObjects to create.
* @param usingDefaultsClassName The name of the defaults class to use.
* @param forceInsert Whether to force insert the created SObjects.
* @param preventFakeId Whether to prevent fake ID generation when not inserting.
*
* @return A list of created SObjects.
*/
@SuppressWarnings('PMD.ExcessiveParameterList')
public static List<SObject> createSObjects(
SObject prototype,
Integer count,
String usingDefaultsClassName,
Boolean forceInsert,
Boolean preventFakeId
) {
List<SObject> createdSObjects = new List<SObject>();
SObject constructedFromPrototype = internalCreateSObject(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

Note: This review was outside of the patch, so it was mapped to the patch with the greatest overlap. Original lines [195-232]

Similar to the previous comment, the addition of the preventFakeId parameter in the createSObjects method provides more flexibility when creating a list of SObjects. Again, make sure all calls to this method have been updated to include this new parameter.

Comment on lines +34 to +51
) {
return mutateCloneToRespectNameAndAutonumberRules(clonedSObject, fakeId, iterationCounter, false);
}

/**
* @description responsible for enforcing a cloned SObject's values honor uniqueness rules for name and
* autonumber fields
* @param clonedSObject SObject a cloned version of the prototyped SObject
* @param fakeId Boolean Populates a fakeId on the object unless it's to be inserted
* @param iterationCounter Integer Counter for ensuring name uniqueness
* @param preventFakeId Boolean Prevents fake ID generation even when fakeId is true
* @return SObject a mutated version of the cloned object with unique name and autonumber fields
*/
public static SObject mutateCloneToRespectNameAndAutonumberRules(
SObject clonedSObject,
Boolean fakeId,
Integer iterationCounter,
Boolean preventFakeId
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The new method mutateCloneToRespectNameAndAutonumberRules has been overloaded to include a preventFakeId parameter. This allows for more granular control over whether a fake ID should be generated or not. The logic seems sound and the parameter is well-documented in the method description.

+	public static SObject mutateCloneToRespectNameAndAutonumberRules(
+		SObject clonedSObject,
+		Boolean fakeId,
+		Integer iterationCounter,
+		Boolean preventFakeId
+	) {
+		String nameField = calculateNameField(clonedSObject);
+		Boolean isNameFieldAutoNumber = nameFieldIsAutoNumber(

);
}
if (fakeId) {
if (fakeId && !preventFakeId) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The condition for generating a fake ID has been updated to also check if preventFakeId is false. This ensures that a fake ID is only generated when both fakeId is true and preventFakeId is false, providing an additional layer of control over this operation.

-		if (fakeId) {
+		if (fakeId && !preventFakeId) {
 			clonedSObject.Id = IdFactory.get(clonedSObject);
 		}
 		return clonedSObject;

Comment on lines +64 to +68
* want executed asynchronously. The returned Object will be passed as dataPassthrough to the next step.
*
* @return Object - data to pass through to the next step in the process
*/
abstract public void execute();
abstract public Object execute();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The change from void to Object as the return type for the execute() method is a breaking change. This will require all classes extending QueueableProcess to update their implementation of this method to return an Object. Ensure that this change is communicated clearly to all developers who are using this class.

- abstract public void execute();
+ abstract public Object execute();

Comment on lines +90 to 107
this.queueableContextHistory = this.queueableContextHistory ??
new List<QueueableContext>();
this.queueableContextHistory.add(context);

// Call the abstract method `execute` and capture its return value as the dataPassthrough for the next step.
Object nextDataPassthrough = execute();

// create a new instance of the finalizer class. Note that we're passing in the list of remaining steps and the
// passthrough data.
// returned data from execute() as the passthrough data for the next step.
Finalizer nextStep = new EnqueueNextQueueableProcessStep(
this.processSteps,
this.dataPassthrough,
nextDataPassthrough,
this.queueableContextHistory,
this.finalizerContextHistory
);
// Attach the finalizer to system context. This will take care of enqueueing the next QueueableProcess step in
// the nextStep.
System.attachFinalizer(nextStep);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The refactoring in these lines improves the code by removing the need for an explicit null check on queueableContextHistory. The use of the null-coalescing operator (??) simplifies the code and makes it more readable. However, the change in how the execute() method is called (now capturing its return value) could potentially introduce bugs if the returned object is not handled correctly in subsequent steps. Make sure to thoroughly test this new behavior.

- if (this.queueableContextHistory == null) {
-    this.queueableContextHistory = new List<QueueableContext>();
- }
- this.queueableContextHistory.add(context);
- Finalizer nextStep = new EnqueueNextQueueableProcessStep(
-    this.processSteps,
-    this.dataPassthrough,
-    this.queueableContextHistory,
-    this.finalizerContextHistory
- );
- System.attachFinalizer(nextStep);
- execute();

+ this.queueableContextHistory = this.queueableContextHistory ??
+    new List<QueueableContext>();
+ this.queueableContextHistory.add(context);
+ Object nextDataPassthrough = execute();
+ Finalizer nextStep = new EnqueueNextQueueableProcessStep(
+    this.processSteps,
+    nextDataPassthrough,
+    this.queueableContextHistory,
+    this.finalizerContextHistory
+ );
+ System.attachFinalizer(nextStep);

@codefriar codefriar merged commit 4af5c03 into main Jun 9, 2025
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant