class CreateMaintenanceTasks < ActiveRecord::Migration[7.0]
def change
create_table :maintenance_tasks do |t|
t.string :name, null: false
t.string :state, null: false, default: "pending"
t.datetime :started_at
t.datetime :finished_at
t.text :error_message
t.timestamps
end
add_index :maintenance_tasks, :name, unique: true
end
end
class MaintenanceTask < ApplicationRecord
STATES = %w[pending running finished failed].freeze
validates :name, presence: true, uniqueness: true
validates :state, inclusion: { in: STATES }
def self.run_once(name)
return if finished?(name)
with_advisory_lock(name) do
return if finished?(name)
task = find_or_create_by!(name: name)
task.update!(state: "running", started_at: Time.current, error_message: nil)
begin
yield task
task.update!(state: "finished", finished_at: Time.current)
rescue => e
task.update!(state: "failed", error_message: e.message)
raise
end
end
end
def self.finished?(name)
where(name: name, state: "finished").exists?
end
def self.with_advisory_lock(name)
key = Zlib.crc32(name)
connection.execute("SELECT pg_advisory_lock(#{key})")
yield
ensure
connection.execute("SELECT pg_advisory_unlock(#{key})")
end
end
class BackfillUserSlugsTask < ApplicationJob
queue_as :maintenance
def perform
MaintenanceTask.run_once("backfill_user_slugs") do
User.where(slug: nil).find_each(batch_size: 500) do |user|
candidate = user.name.parameterize
candidate = "#{candidate}-#{user.id}" if User.exists?(slug: candidate)
user.update_column(:slug, candidate)
end
end
rescue => e
Rails.logger.error("backfill_user_slugs failed: #{e.message}")
raise
end
end
Schema migrations are great for structural changes, but heavy data backfills do not belong inline in a db:migrate run — they lock deploys, time out, and are hard to retry. This snippet shows the common alternative: durable, database-backed maintenance tasks that are guaranteed to run exactly once, tracked in their own table rather than in the schema migration history.
The create_maintenance_tasks migration tab defines the tracking table. Each task has a unique name, a state, and timestamps for started_at/finished_at. The unique index on name is the backbone of idempotency: even under a race between two app servers booting simultaneously, the database refuses a second row with the same name, so a task can only be claimed once.
The MaintenanceTask model tab wraps that table with the claim logic. MaintenanceTask.run_once is the public entry point: it first checks whether a finished record exists and returns early if so, making the whole thing a no-op on subsequent deploys. To coordinate concurrent processes it takes a Postgres advisory lock via with_advisory_lock, keyed on a hash of the task name. Advisory locks are session-scoped and cheap, and they let the code serialize the critical section without locking any real table rows. Inside the lock it re-checks completion (the double-check guards against a task that finished while waiting on the lock), then transitions the record through running to finished, capturing error_message and leaving the state as failed if the block raises so a later run can retry.
The BackfillUserSlugsTask job tab is a realistic consumer: an ActiveJob that calls run_once('backfill_user_slugs') and does the actual work in batches with find_each to keep memory flat. Enqueuing this job from a deploy hook or a rake task means the backfill happens asynchronously, retries safely, and never blocks a migration.
The trade-off is that these tasks live outside schema.rb, so they must be cleaned up manually once fully rolled out. The pattern shines when a backfill is large, slow, or must survive partial failures — situations where an inline migration would be fragile.
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.