ruby 100 lines · 3 tabs

Session-Backed Shopping Cart with Sinatra Helpers and a Cart Value Object

Shared by codesnips Aug 2026
3 tabs
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
3 files · ruby Explain with highlit

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

Share this code

Here's the card — post it anywhere.

Session-Backed Shopping Cart with Sinatra Helpers and a Cart Value Object — share card
Link copied