class Cart < ApplicationRecord
TTL = 30.minutes
has_many :line_items, dependent: :destroy
enum status: { active: 0, expired: 1, checked_out: 2 }
scope :active, -> { where(status: statuses[:active]).where("last_active_at > ?", TTL.ago) }
scope :stale, -> { where(status: statuses[:active]).where("last_active_at <= ?", TTL.ago) }
def touch_activity!
update_column(:last_active_at, Time.current)
end
def expired?
active? && last_active_at <= TTL.ago
end
def expire_if_stale!
return false unless expired?
with_lock do
return false unless expired? # re-check under row lock
expire!
end
true
end
def expire!
transaction do
line_items.find_each { |item| item.release_reservation! }
update!(status: :expired, expired_at: Time.current)
end
end
end
class CartSweeperJob < ApplicationJob
queue_as :low
SWEEP_INTERVAL = 5.minutes
def perform
Cart.stale.in_batches(of: 500) do |relation|
relation.each do |cart|
begin
cart.expire_if_stale!
rescue => e
Rails.logger.error("[CartSweeper] cart=#{cart.id} failed: #{e.class}: #{e.message}")
Sentry.capture_exception(e) if defined?(Sentry)
end
end
end
ensure
self.class.set(wait: SWEEP_INTERVAL).perform_later
end
end
class CartsController < ApplicationController
before_action :set_cart
def show
render json: serialize(@cart)
end
def add_item
@cart.touch_activity!
item = @cart.line_items.create!(line_item_params)
render json: serialize(item), status: :created
end
def remove_item
@cart.touch_activity!
@cart.line_items.find(params[:item_id]).destroy!
head :no_content
end
private
def set_cart
@cart = current_user.carts.find(params[:id])
if @cart.expire_if_stale!
@cart = current_user.carts.create!(status: :active, last_active_at: Time.current)
end
end
def line_item_params
params.require(:line_item).permit(:product_id, :quantity)
end
def serialize(record)
CartSerializer.new(record).as_json
end
end
This snippet shows a common e-commerce housekeeping problem: shopping carts that sit untouched must eventually expire so that reserved inventory is released and stale carts don't clutter checkout. The approach combines a lazy TTL check that happens whenever a cart is read with an active sweeper job that periodically cleans up in bulk. Neither strategy alone is enough — the lazy check keeps individual sessions correct in real time, while the sweeper guarantees abandoned carts eventually get collected even if no one ever touches them again.
The Cart model treats last_active_at as the source of truth for freshness. TTL defines the inactivity window, and touch_activity! bumps the timestamp on every meaningful interaction so an active shopper never sees their cart vanish. expired? is a pure comparison against TTL.ago, keeping the rule in one place. The important detail is expire_if_stale!: it is called on read paths and, inside a transaction with lock!, re-checks expired? before acting. That double-check under a row lock avoids a race where two concurrent requests both try to expire the same cart. The expire! method releases inventory via line_items and flips the status, and the active / stale scopes push the TTL predicate into SQL so queries stay index-friendly on last_active_at.
The CartSweeperJob is the active half. It selects Cart.stale in batches with in_batches, and calls expire_if_stale! per record rather than a blind bulk UPDATE so that inventory release and any callbacks run correctly. find_each-style batching bounds memory, and rescuing per-cart means one bad row won't abort the whole sweep. It reschedules itself, making it safe to enqueue from a scheduler.
The CartsController wires the lazy check into the request cycle: set_cart loads the cart and immediately calls expire_if_stale!, so a returning shopper past the TTL gets a clean empty cart instead of stale reservations. Write actions call touch_activity! to keep live carts alive.
The trade-off is eventual rather than exact expiry — a cart may live slightly past its TTL until read or swept. For carts that reserve stock, tune the sweep frequency to the acceptable reservation slack, and rely on the row lock to keep concurrent expirations idempotent.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
Share this code
Here's the card — post it anywhere.