class FeatureFlag < ApplicationRecord
validates :key, presence: true, uniqueness: true
validates :percentage, inclusion: { in: 0..100 }
after_commit :expire_cache
def enabled_for?(actor_id)
return false unless enabled
return true if percentage >= 100
return false if percentage <= 0
bucket(actor_id) < percentage
end
def cache_key
self.class.cache_key_for(key)
end
def self.cache_key_for(flag_key)
"feature_flag/v2/#{flag_key}"
end
private
def bucket(actor_id)
seed = "#{key}:#{actor_id}"
Digest::MD5.hexdigest(seed).to_i(16) % 100
end
def expire_cache
Rails.cache.delete(cache_key)
end
end
class FlagResolver
CACHE_TTL = 5.minutes
DEFAULTS = {
"new_checkout" => false,
"beta_dashboard" => false
}.freeze
def self.enabled?(flag_key, actor_id: nil)
flag = read_flag(flag_key)
return default_for(flag_key) if flag.nil?
flag.enabled_for?(actor_id)
rescue StandardError => e
Rails.logger.warn("[FlagResolver] falling back to default for #{flag_key}: #{e.class}")
default_for(flag_key)
end
def self.read_flag(flag_key)
Rails.cache.fetch(FeatureFlag.cache_key_for(flag_key), expires_in: CACHE_TTL) do
# nil is cached too, to avoid repeated misses for unknown keys
FeatureFlag.find_by(key: flag_key)
end
end
def self.default_for(flag_key)
DEFAULTS.fetch(flag_key, false)
end
end
module FeatureGating
extend ActiveSupport::Concern
included do
helper_method :feature_enabled? if respond_to?(:helper_method)
end
def feature_enabled?(flag_key)
FlagResolver.enabled?(flag_key, actor_id: current_user&.id)
end
def require_feature!(flag_key)
return if feature_enabled?(flag_key)
raise ActionController::RoutingError, "feature #{flag_key} is disabled"
end
end
class CheckoutController < ApplicationController
include FeatureGating
before_action -> { require_feature!("new_checkout") }, only: :create
def create
order = Orders::Create.call(current_user, params[:cart_id])
redirect_to order_path(order)
end
end
This snippet shows a feature flag system built around a read path that stays fast under normal load but degrades gracefully when the cache is unavailable. The design goal is that a flag lookup should never take down a request: it prefers the cache, falls back to the database, and finally falls back to a static default so that even a total datastore outage produces a safe answer.
In FeatureFlag model, each flag is a durable Postgres row keyed by a unique key, with a boolean enabled and an integer percentage for gradual rollouts. The enabled_for? method centralizes the rollout decision: a fully disabled flag short-circuits to false, a 100% flag is a simple boolean, and anything in between is bucketed deterministically. The bucketing uses an MD5 digest of the flag key plus the actor id, converted to an integer mod 100, so the same actor always lands in the same bucket for a given flag — that stability is what makes percentage rollouts feel consistent rather than flickering on every request. An after_commit hook calls expire_cache so writes invalidate the cached copy immediately.
In FlagResolver service, enabled? implements the layered read. It asks Rails.cache.fetch first, and the block behind that fetch hits the database only on a miss, memoizing the row for CACHE_TTL. The whole thing is wrapped in a rescue that logs and returns a hardcoded DEFAULTS value if both cache and DB raise — this is the crucial resilience property. read_flag caches even the absence of a flag (as nil) to avoid a stampede of DB lookups for unknown keys, a common source of accidental load.
In ApplicationController concern, feature_enabled? wires the resolver into request handling using current_user&.id as the actor, and require_feature! turns a flag into a routing guard that raises ActionController::RoutingError when the feature is off. The trade-off is mild staleness: after a flag flips, cached copies live up to CACHE_TTL on nodes that did not process the invalidation, which is acceptable for flags but not for security-critical gates. This pattern fits any system that reads flags on the hot path and cannot tolerate a cache outage becoming a full outage.
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.