ruby
37 lines · 7 steps
Merging a guest cart into a user cart in Rails
A service object that folds an anonymous cart's items into a logged-in user's cart atomically, respecting stock limits.
Explained by
highlit
1class CartMergeService
2 def initialize(guest_cart:, user_cart:)
3 @guest_cart = guest_cart
4 @user_cart = user_cart
5 end
6
7 def call
8 return @user_cart if @guest_cart.nil? || @guest_cart == @user_cart
9
10 ActiveRecord::Base.transaction do
11 @guest_cart.line_items.includes(:variant).find_each do |guest_item|
12 merge_line_item(guest_item)
13 end
14
15 @guest_cart.destroy!
16 end
17
18 @user_cart.reload
19 end
20
21 private
22
23 def merge_line_item(guest_item)
24 existing = @user_cart.line_items.find_by(variant_id: guest_item.variant_id)
25
26 if existing
27 new_quantity = [existing.quantity + guest_item.quantity, guest_item.variant.stock].min
28 existing.update!(quantity: new_quantity)
29 else
30 @user_cart.line_items.create!(
31 variant: guest_item.variant,
32 quantity: [guest_item.quantity, guest_item.variant.stock].min,
33 price: guest_item.variant.current_price
34 )
35 end
36 end
37end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Wrapping multi-step writes in a transaction keeps the merge all-or-nothing, so a failure never leaves both carts half-updated.
- 2Guarding against nil and identical inputs up front makes the operation safe to call repeatedly without side effects.
- 3Clamping quantities to available stock at merge time prevents the combined cart from exceeding what can actually ship.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
ruby
class ApplicationController < ActionController::Base EXPERIMENTS = { checkout_button_color: %w[control blue green], onboarding_flow: %w[control streamlined]
How A/B test cohorts are assigned in Rails
a-b-testing
cookies
hashing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/merging-a-guest-cart-into-a-user-cart-in-rails-explained-ruby-1213/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.