require 'sinatra/base'
require_relative 'cart'
require_relative 'cart_helpers'
class StoreApp < Sinatra::Base
enable :sessions
set :session_secret, ENV.fetch('SESSION_SECRET', 'change-me-in-production-0123456789abcdef')
helpers CartHelpers
get '/cart' do
erb :cart, locals: { items: current_products, total: cart.total }
end
post '/cart/add' do
cart.add(params[:product_id].to_i, quantity: params.fetch(:quantity, 1).to_i)
redirect '/cart'
end
post '/cart/update' do
cart.set(params[:product_id].to_i, params[:quantity].to_i)
redirect '/cart'
end
post '/cart/remove' do
cart.remove(params[:product_id].to_i)
redirect '/cart'
end
post '/cart/clear' do
cart.clear
redirect '/cart'
end
end
require_relative 'cart'
module CartHelpers
def cart
session[:cart] ||= {}
@cart ||= Cart.new(session[:cart])
end
def current_products
cart.entries.map do |product_id, quantity|
product = Product.find(product_id)
next unless product
{ product: product, quantity: quantity, line_total: product.price * quantity }
end.compact
end
def cart_count
cart.item_count
end
end
class Cart
def initialize(store)
@store = store # the live session hash: { product_id => quantity }
end
def add(product_id, quantity: 1)
set(product_id, (@store[product_id] || 0) + quantity)
end
def set(product_id, quantity)
quantity = quantity.to_i
if quantity <= 0
remove(product_id)
else
@store[product_id] = quantity
end
end
def remove(product_id)
@store.delete(product_id)
end
def clear
@store.clear
end
def entries
@store
end
def item_count
@store.values.sum
end
def total
@store.reduce(0) do |sum, (product_id, quantity)|
product = Product.find(product_id)
product ? sum + (product.price * quantity) : sum
end
end
def empty?
@store.empty?
end
end
This snippet shows how a shopping cart survives across HTTP requests in a stateless web app by leaning on Sinatra's cookie-backed sessions, while keeping route handlers thin. The core idea is that the raw session is just a serialization boundary: it stores a plain Hash of product_id => quantity, and everything else is derived from that on each request. HTTP is stateless, so without a persistence mechanism the cart would vanish between the add-to-cart POST and the checkout GET; the session cookie carries a signed identifier and Sinatra rehydrates session on every request.
In app.rb, enable :sessions turns on Rack::Session::Cookie, and set :session_secret supplies the HMAC key that signs the cookie so a client cannot tamper with quantities. The routes themselves stay declarative — post '/cart/add' coerces params and delegates to cart.add, then issues a redirect following the Post/Redirect/Get pattern so a browser refresh does not resubmit the form. Notice the routes never touch session[:cart] directly; they go through the cart helper.
That helper lives in CartHelpers, registered via helpers. The cart method is memoized per request with @cart ||= and wraps the mutable session hash in a Cart value object. Because Cart mutates the exact hash instance held in session[:cart], changes are written back automatically — Rack reserializes the session at the end of the request. current_products bridges cart entries to the product catalog for rendering.
Cart in cart.rb is where the real logic sits, isolated from HTTP. It normalizes quantities, deletes entries when quantity drops to zero, and computes total and item_count by folding over the store. Keeping money math and validation here makes the class unit-testable without booting Rack.
The main trade-off is that cookie sessions are size-limited (~4KB) and sent on every request, so only lightweight identifiers belong there — never full product records. For larger carts a server-side store like Rack::Session::Pool or Redis is the natural upgrade, and this design swaps cleanly because only the session backend changes, not Cart.
Related snips
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
Django URL namespacing and reverse lookups
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
import SwiftUI
struct ContentView: View {
@State private var username = ""
@State private var isLoggedIn = false
@StateObject private var viewModel = LoginViewModel()
SwiftUI declarative UI with state management
import { Navigate, useLocation } from 'react-router-dom'
import { useAuth } from '@/contexts/AuthContext'
interface ProtectedRouteProps {
children: React.ReactNode
}
React Router with protected routes
Share this code
Here's the card — post it anywhere.