Skip to content

Recalculate the order once per cart change - #6526

Open
ikraamg wants to merge 1 commit into
solidusio:mainfrom
ikraamg:perf/cart-single-recalculate
Open

Recalculate the order once per cart change#6526
ikraamg wants to merge 1 commit into
solidusio:mainfrom
ikraamg:perf/cart-single-recalculate

Conversation

@ikraamg

@ikraamg ikraamg commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Adding, removing or updating a cart item recalculated the order twice: once so promotion rules see correct totals, and once to fold in any adjustment the promotion handler created. The second pass is only needed when the handler actually changed an adjustment, and it already computes that signal and discards it.

  • PromotionHandler::Cart#activate now returns whether any promotion created or changed an adjustment
  • the second recalculation only runs when it did

Three things in the diff are worth calling out.

check_shipments_and_restart_checkout now runs before the recalculation rather than after. A cart change on an order past the cart step destroys its pending shipments and zeroes shipment_total with update_column, which does not touch total. With a single recalculation it has to happen after the shipments are gone, or the destroyed shipment's cost stays stranded in the total. A spec covers this. The promotion handler still runs after the shipment check, so the behaviour 0a9149b added for stores that removed the address state is unchanged, and the recalculation still precedes the handler so ItemTotal rules see correct totals.

This makes PromotionAction#perform's return contract load-bearing for order totals. The contract is already documented in Spree::Promotion#activate and all three core actions honour it, but a custom action that creates an adjustment and returns something other than true was previously rescued by the unconditional second recalculation and no longer is.

PromotionHandler::Cart#activate changes return type, from the promotions array to a boolean. Spree::OrderContents is its only caller in the codebase.

Adding one item to a cart that already holds ten drops recalculations from 2 to 1, uncached queries from 8 to 7, and allocations from 9,987 to 6,305. On a five item cart it is 8,593 to 5,608. Measured on seeded variants with GC disabled, best of nine; the allocation counts reproduce exactly across runs. A cart build performs one recalculation per item added, so every one of them pays the saving.

Specs cover the recalculation count for add, update and remove, that the persisted totals match what a further full recalculation would write, that an automatic ItemTotal promotion still applies once a quantity change crosses its threshold, and that a cart change past the cart step keeps the destroyed shipment's cost out of the persisted total. All were confirmed to fail without the fix.

Adding, removing or updating a cart item recalculated the order twice: once
before the cart promotion handler ran and once after. The second pass is only
needed when a promotion actually created or changed an adjustment.

PromotionHandler::Cart#activate already worked out whether any promotion took
an action but discarded it, so it now returns that, and OrderContents
recalculates a second time only when it is true.

The remaining recalculation runs after the shipment changes, matching
SimpleOrderContents, so shipment_total is up to date, and before the promotion
handler, so ItemTotal rules see the correct item total.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ikraamg
ikraamg requested a review from a team as a code owner July 30, 2026 16:02
@github-actions github-actions Bot added the changelog:solidus_legacy_promotions Changes to the solidus_legacy_promotions gem label Jul 30, 2026
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.38%. Comparing base (72f9fcd) to head (c0966eb).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6526      +/-   ##
==========================================
- Coverage   95.07%   92.38%   -2.70%     
==========================================
  Files         460       42     -418     
  Lines        7924      788    -7136     
==========================================
- Hits         7534      728    -6806     
+ Misses        390       60     -330     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:solidus_legacy_promotions Changes to the solidus_legacy_promotions gem

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant