class DiscountCode < ApplicationRecord
has_many :redemptions, dependent: :destroy
enum discount_type: { percentage: 0, fixed: 1 }
validates :code, presence: true, uniqueness: { case_sensitive: false }
validates :value_cents, numericality: { greater_than: 0 }
validates :usage_limit, numericality: { greater_than: 0 }, allow_nil: true
before_validation { self.code = code.to_s.strip.upcase }
def active?(at: Time.current)
enabled? &&
(starts_at.nil? || starts_at <= at) &&
(expires_at.nil? || expires_at >= at)
end
def usage_available?
usage_limit.nil? || redemptions.count < usage_limit
end
def amount_for(subtotal_cents)
raw =
if percentage?
(subtotal_cents * value_cents / 100.0).round
else
value_cents
end
[raw, subtotal_cents].min
end
end
class Redemption < ApplicationRecord
belongs_to :discount_code
belongs_to :order
belongs_to :customer
validates :amount_cents, numericality: { greater_than: 0 }
validates :order_id, uniqueness: true
validates :customer_id,
uniqueness: { scope: :discount_code_id,
message: "has already redeemed this code" }
end
class DiscountRedemptionService
Result = Struct.new(:success?, :redemption, :error, keyword_init: true)
def initialize(order:, raw_code:)
@order = order
@raw_code = raw_code.to_s.strip.upcase
end
def call
code = DiscountCode.find_by(code: @raw_code)
return failure(:not_found) if code.nil?
ActiveRecord::Base.transaction do
code.lock! # SELECT ... FOR UPDATE
return failure(:inactive) unless code.active?
return failure(:exhausted) unless code.usage_available?
redemption = code.redemptions.create!(
order: @order,
customer: @order.customer,
amount_cents: code.amount_for(@order.subtotal_cents)
)
@order.update!(discount_cents: redemption.amount_cents)
success(redemption)
end
rescue ActiveRecord::RecordInvalid
failure(:already_redeemed)
end
private
def success(redemption)
Result.new(success?: true, redemption: redemption)
end
def failure(reason)
Result.new(success?: false, error: reason)
end
end
class OrdersController < ApplicationController
before_action :set_order
def apply_discount
result = DiscountRedemptionService.new(
order: @order,
raw_code: params.require(:code)
).call
if result.success?
redirect_to @order, notice: "Discount applied: #{helpers.humanized_money(result.redemption.amount_cents)}"
else
redirect_to @order, alert: error_message(result.error)
end
end
private
def set_order
@order = current_customer.orders.find(params[:order_id])
end
def error_message(reason)
{
not_found: "That code doesn't exist.",
inactive: "That code is not currently active.",
exhausted: "That code has reached its usage limit.",
already_redeemed: "This order already has a discount applied."
}.fetch(reason, "Unable to apply that code.")
end
end
This snippet shows how a discount code is applied to an order using a redeemable model plus a service that enforces the business rules under concurrency. The core problem is race conditions: two shoppers can try to redeem the last available use of a code at the same time, and naive count checks let both succeed. The design pushes correctness down to the database with row locks and a redemption ledger.
In DiscountCode model, each code carries an optional usage_limit, a validity window (starts_at/expires_at), and a discount_type enum for percentage or fixed amounts. The active? predicate combines the enabled flag with the time window so the service has a single question to ask. amount_for computes the discount against a subtotal and clamps a fixed discount so it never exceeds the order total, avoiding negative charges. Redemptions are tracked through a has_many :redemptions association rather than a mutable counter, which makes each use auditable and lets a unique index prevent a customer from redeeming the same code twice.
In Redemption model, the join between a discount_code and an order records the amount_cents actually granted. The validates_uniqueness_of :order_id guard means an order can only ever hold one redemption, so re-running the apply flow is idempotent at the schema level.
In DiscountRedemptionService, call wraps everything in a transaction and immediately calls lock! on the code row, taking a SELECT ... FOR UPDATE. Holding that lock, it re-checks active? and compares redemptions.count against usage_limit, so the limit check and the insert happen atomically — no two concurrent callers can both pass the check. A Result struct communicates success or a symbolic failure reason instead of raising for expected outcomes, while a genuine RecordInvalid (such as a duplicate order redemption) is caught and mapped to :already_redeemed.
In OrdersController, the apply_discount action delegates to the service and branches on result.success?, keeping controller logic thin. The trade-off is that lock! serializes redemptions of a single hot code, but that contention is precisely what guarantees the usage limit is honored. This pattern fits any scarce-resource redemption — coupons, invites, seat allocations — where correctness matters more than raw throughput.
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
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
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.