class AddUniqueIndexToTags < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def up
execute <<~SQL
UPDATE tags SET name = LOWER(TRIM(name)) WHERE name IS NOT NULL;
SQL
add_index :tags, :name,
unique: true,
algorithm: :concurrently,
name: "index_tags_on_lower_name"
end
def down
remove_index :tags, name: "index_tags_on_lower_name"
end
end
class Tag < ApplicationRecord
MAX_RETRIES = 3
validates :name, presence: true, uniqueness: { case_sensitive: false }
before_validation :normalize_name
def self.find_or_create_safely(name, attrs = {})
normalized = name.to_s.strip.downcase
attempts = 0
begin
attempts += 1
find_or_create_by(name: normalized) do |tag|
tag.assign_attributes(attrs)
end
rescue ActiveRecord::RecordNotUnique
existing = find_by(name: normalized)
return existing if existing
retry if attempts < MAX_RETRIES
raise
end
end
private
def normalize_name
self.name = name.to_s.strip.downcase if name.present?
end
end
class TagsController < ApplicationController
def create
was_new = false
tag = Tag.find_or_create_safely(tag_params[:name], color: tag_params[:color])
was_new = tag.previous_changes.key?("id")
if tag.persisted?
render json: tag, status: (was_new ? :created : :ok)
else
render json: { errors: tag.errors.full_messages }, status: :unprocessable_entity
end
end
private
def tag_params
params.require(:tag).permit(:name, :color)
end
end
The classic find_or_create_by in ActiveRecord has a well-known gap: between the SELECT that checks for an existing row and the INSERT that creates it, another process can win the race and insert the same record. Under concurrency — parallel web requests, multiple background workers, retried jobs — this produces either duplicate rows or a RecordNotUnique exception surfacing to the user. The only durable fix is to let the database enforce uniqueness and then handle the collision gracefully.
The db/migrate/add_unique_index_to_tags.rb migration establishes that guarantee. It normalizes existing data with LOWER(name) before adding the index so the backfill does not fail, then creates a unique index using algorithm: :concurrently. Because concurrent index builds cannot run inside a transaction, disable_ddl_transaction! is required — a common pitfall when adding indexes to live Postgres tables without locking writes.
In Tag model, the retry logic lives in find_or_create_safely. It first attempts a normal find_or_create_by; if two callers race, the loser raises ActiveRecord::RecordNotUnique, which the rescue catches and re-reads the now-committed row with find_by. The bounded loop matters: on the rare occasion the row is deleted between the failed insert and the re-read, the method retries rather than returning nil or looping forever. Normalization happens in a before_validation callback so the value written matches exactly what the index enforces, avoiding case-sensitivity mismatches.
TagsController shows the caller. create simply delegates to Tag.find_or_create_safely, so the endpoint is naturally idempotent — repeated submissions converge on one row. It responds :created or :ok depending on whether a new record was actually inserted, giving clients honest semantics.
The trade-off is that this pattern relies on the exception path for correctness, and exceptions are comparatively expensive; under heavy contention it can also churn savepoints inside a surrounding transaction. For most workloads the collision is rare, so the cost is negligible, and the correctness guarantee — no duplicates, ever — comes from the index rather than from hopeful application logic.
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.