class AddConstraintsToUsers < ActiveRecord::Migration[6.1]
def change
# Null constraints
change_column_null :users, :email, false
change_column_null :users, :username, false
# Unique constraints
add_index :users, :email, unique: true
add_index :users, 'LOWER(username)', unique: true, name: 'index_users_on_lower_username'
# Check constraints
execute <<-SQL
ALTER TABLE users
ADD CONSTRAINT check_email_format
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
SQL
execute <<-SQL
ALTER TABLE users
ADD CONSTRAINT check_username_length
CHECK (char_length(username) >= 3 AND char_length(username) <= 30);
SQL
end
end
While ActiveRecord validations catch most invalid data, database constraints provide a safety net that prevents invariant violations even when validations are bypassed. I add null: false constraints for required columns, unique indexes for uniqueness constraints, and check constraints for domain validations like age > 0. Foreign key constraints with foreign_key: true prevent orphaned records when associations are deleted. The combination of validations (fast, user-friendly errors) and constraints (absolute guarantees) provides defense in depth. When a constraint is violated, Rails raises ActiveRecord::NotNullViolation or similar exceptions that I handle in error handlers. Constraints are especially important in applications with background jobs or data imports that might skip validation.
Related snips
-- Simple function
CREATE OR REPLACE FUNCTION get_full_name(
first_name VARCHAR,
last_name VARCHAR
)
RETURNS VARCHAR AS $$
Stored procedures and functions in PostgreSQL
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
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.