ruby 87 lines · 3 tabs

Merge a Guest Cart Into a User's Cart on Login With a Rails Service Object

Shared by codesnips Aug 2026
3 tabs
class Cart < ApplicationRecord
  belongs_to :user, optional: true
  has_many :cart_items, dependent: :destroy

  scope :for_session, ->(token) { where(session_token: token) }
  scope :active, -> { where(merged_at: nil) }

  def line_item_for(variant_id)
    cart_items.find_by(variant_id: variant_id)
  end

  def merged?
    merged_at.present?
  end

  def merged!
    update!(merged_at: Time.current)
  end

  def total_quantity
    cart_items.sum(:quantity)
  end
end
3 files · ruby Explain with highlit

This snippet shows the common e-commerce problem of reconciling an anonymous shopping cart with a returning customer's saved cart at the moment they authenticate. Before login a visitor accumulates items against a cart keyed by session; after login those items must be folded into the persistent cart tied to their account without dropping quantities or creating duplicate line items.

The CartMerger service in the first tab is a plain-old Ruby object that takes the two carts and does the reconciliation in a single database transaction. Wrapping the work in ActiveRecord::Base.transaction guarantees the merge is atomic: if merging any line item fails, the whole operation rolls back and the customer is never left with a half-merged cart. For each guest line item it looks for a matching CartItem on the user cart by variant_id; when found it increments quantity, otherwise it re-parents the row by updating cart_id. The merged? check on the guest cart makes the operation idempotent so a retried callback cannot double-count. Finally the emptied guest cart is destroyed.

The SessionsController tab overrides Devise's controller and hooks into after_sign_in_path_for, the canonical place to run post-authentication side effects. It pulls the guest cart out of the session via current_guest_cart, hands both carts to CartMerger, and clears the stale :guest_cart_id from the session so the reference cannot leak into the next visit. Guarding on the presence of a guest cart keeps logins fast for users who arrive with an empty session.

The Cart model tab supplies the domain methods the other files lean on: line_item_for for the lookup-or-nil pattern, merged! to flip the idempotency flag, and a scope for locating carts by session token. Keeping this behavior on the model keeps the service thin and the controller ignorant of persistence details.

The trade-off is that merging by variant_id assumes line items are uniquely identified by variant; carts that allow per-line customization (gift notes, engraving) would need a richer match key. Running the merge inside the sign-in path also couples cart logic to authentication, so high-traffic stores often move the same service into a background job triggered by an event instead.


Related snips

Share this code

Here's the card — post it anywhere.

Merge a Guest Cart Into a User's Cart on Login With a Rails Service Object — share card
Link copied