class CreateIdempotencyKeys < ActiveRecord::Migration[7.1]
def change
create_table :idempotency_keys do |t|
t.string :key, null: false
t.string :request_path, null: false
t.datetime :locked_at
t.integer :response_status
t.jsonb :response_body
t.references :user, foreign_key: true
t.timestamps
end
add_index :idempotency_keys, [:key, :request_path], unique: true
add_index :idempotency_keys, :created_at
end
end
class IdempotencyKey < ApplicationRecord
belongs_to :user, optional: true
def self.claim!(key:, path:, user_id: nil)
result = insert_all(
[{ key: key, request_path: path, user_id: user_id, locked_at: Time.current,
created_at: Time.current, updated_at: Time.current }],
unique_by: [:key, :request_path],
on_duplicate: :skip
)
record = find_by!(key: key, request_path: path)
fresh_claim = result.rows.any?
[record, fresh_claim]
end
def in_progress?
locked_at.present? && response_status.nil?
end
def completed?
response_status.present?
end
def store_response!(status:, body:)
update!(response_status: status, response_body: body)
end
end
module Idempotent
extend ActiveSupport::Concern
private
def require_idempotency_key
key = request.headers["Idempotency-Key"].presence
return render_missing_key unless key
@idempotency_key, fresh = IdempotencyKey.claim!(
key: key,
path: request.path,
user_id: current_user&.id
)
return if fresh
if @idempotency_key.completed?
render json: @idempotency_key.response_body,
status: @idempotency_key.response_status
else
render json: { error: "Request already in progress" }, status: :conflict
end
end
def store_idempotent_response(status:, body:)
@idempotency_key&.store_response!(status: Rack::Utils.status_code(status), body: body)
end
def render_missing_key
render json: { error: "Idempotency-Key header required" }, status: :bad_request
end
end
class OrdersController < ApplicationController
include Idempotent
before_action :authenticate_user!
before_action :require_idempotency_key, only: :create
def create
order = current_user.orders.create!(order_params)
ChargePaymentJob.perform_later(order.id)
body = OrderSerializer.new(order).as_json
store_idempotent_response(status: :created, body: body)
render json: body, status: :created
rescue ActiveRecord::RecordInvalid => e
body = { errors: e.record.errors.full_messages }
store_idempotent_response(status: :unprocessable_entity, body: body)
render json: body, status: :unprocessable_entity
end
private
def order_params
params.require(:order).permit(:sku, :quantity, :shipping_address_id)
end
end
Duplicate POST requests are one of the most common sources of double-charged orders and duplicate records: a user double-clicks submit, a proxy retries a timed-out request, or a mobile client replays a queued mutation. This snippet enforces exactly-once semantics at the application layer using an idempotency key persisted per client, so replaying the same request returns the original result instead of creating a second row.
The CreateIdempotencyKeys migration establishes the storage. Each row is keyed by a key string plus a request_path so the same UUID can be reused safely across different endpoints. A unique index on [:key, :request_path] is the real enforcement mechanism — the database, not Ruby, guarantees no two concurrent requests can both insert the same key. The locked_at, response_status, and response_body columns let the record double as both a lock and a cache of the completed response.
In IdempotencyKey model, claim! performs an atomic upsert: insert_all with unique_by and on_duplicate: :skip inserts the row only if it does not already exist, and the return value reveals whether this request won the race. store_response! records the final status and body once the action succeeds. Keeping this logic on the model keeps the controller thin.
The Idempotent concern provides require_idempotency_key, wired in as a before_action. It reads the Idempotency-Key header, rejects requests without one, and calls claim!. If the key was already claimed, it replays the stored response verbatim — or returns 409 Conflict if the original request is still in flight (locked_at set but no response yet). Otherwise the action runs normally.
OrdersController shows the integration: before_action :require_idempotency_key, only: :create, then a call to store_idempotent_response after the order is created. A subtle trade-off is deciding when to store — storing on both success and handled failures avoids re-running expensive side effects, but transient 500s should generally NOT be cached so genuine retries can succeed. Old keys should be swept periodically to bound table growth. This pattern shifts correctness from client discipline to a server-side guarantee backed by a unique constraint.
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.