rails-active-record-patterns

Rails Active Record Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rails-active-record-patterns" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-rails-active-record-patterns

Rails Active Record Patterns

Master Active Record patterns for building robust Rails models with proper associations, validations, scopes, and query optimization.

Overview

Active Record is Rails' Object-Relational Mapping (ORM) layer that connects model classes to database tables. It implements the Active Record pattern, where each object instance represents a row in the database and includes both data and behavior.

Installation and Setup

Creating Models

Generate a model with migrations

rails generate model User name:string email:string:uniq

Generate model with associations

rails generate model Post title:string body:text user:references

Run migrations

rails db:migrate

Database Configuration

config/database.yml

default: &default adapter: postgresql encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development: <<: *default database: myapp_development

test: <<: *default database: myapp_test

production: <<: *default database: myapp_production username: myapp password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

Core Patterns

  1. Basic Model Definition

app/models/user.rb

class User < ApplicationRecord

Validations

validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :name, presence: true, length: { minimum: 2, maximum: 50 }

Callbacks

before_save :normalize_email after_create :send_welcome_email

Scopes

scope :active, -> { where(active: true) } scope :recent, -> { order(created_at: :desc).limit(10) }

private

def normalize_email self.email = email.downcase.strip end

def send_welcome_email UserMailer.welcome(self).deliver_later end end

  1. Associations

app/models/user.rb

class User < ApplicationRecord

One-to-many

has_many :posts, dependent: :destroy has_many :comments, dependent: :destroy

Many-to-many through join table

has_many :memberships, dependent: :destroy has_many :organizations, through: :memberships

Has-one

has_one :profile, dependent: :destroy

Polymorphic association

has_many :images, as: :imageable, dependent: :destroy end

app/models/post.rb

class Post < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy has_many :commenters, through: :comments, source: :user

Counter cache

belongs_to :user, counter_cache: true end

app/models/organization.rb

class Organization < ApplicationRecord has_many :memberships, dependent: :destroy has_many :users, through: :memberships end

app/models/membership.rb

class Membership < ApplicationRecord belongs_to :user belongs_to :organization

enum role: { member: 0, admin: 1, owner: 2 } end

  1. Advanced Queries

app/models/post.rb

class Post < ApplicationRecord

Scopes with arguments

scope :by_author, ->(user_id) { where(user_id: user_id) } scope :published_after, ->(date) { where('published_at > ?', date) } scope :with_tag, ->(tag) { joins(:tags).where(tags: { name: tag }) }

Class methods for complex queries

def self.popular(threshold = 100) where('views_count >= ?', threshold) .order(views_count: :desc) end

def self.search(query) where('title ILIKE ? OR body ILIKE ?', "%#{query}%", "%#{query}%") end

Query with joins and includes

def self.with_user_and_comments includes(:user, comments: :user) .order(created_at: :desc) end end

Usage

Post.published_after(1.week.ago) .by_author(current_user.id) .with_tag('rails') .popular(50)

  1. Validations

app/models/user.rb

class User < ApplicationRecord

Presence validation

validates :email, :name, presence: true

Uniqueness validation

validates :email, uniqueness: { case_sensitive: false }

Format validation

validates :username, format: { with: /\A[a-z0-9_]+\z/, message: "only allows lowercase letters, numbers, and underscores" }

Length validation

validates :bio, length: { maximum: 500 } validates :password, length: { minimum: 8 }, if: :password_required?

Numericality validation

validates :age, numericality: { only_integer: true, greater_than_or_equal_to: 18, less_than: 120 }

Custom validation

validate :email_domain_allowed

private

def email_domain_allowed return if email.blank?

domain = email.split('@').last
unless ALLOWED_DOMAINS.include?(domain)
  errors.add(:email, "domain #{domain} is not allowed")
end

end

def password_required? new_record? || password.present? end end

  1. Callbacks

app/models/post.rb

class Post < ApplicationRecord

Before callbacks

before_validation :normalize_title before_save :calculate_reading_time before_create :generate_slug

After callbacks

after_create :notify_followers after_update :clear_cache, if: :saved_change_to_body? after_destroy :cleanup_attachments

Around callbacks

around_save :log_save_time

private

def normalize_title self.title = title.strip.titleize if title.present? end

def calculate_reading_time return unless body_changed? words = body.split.size self.reading_time = (words / 200.0).ceil end

def generate_slug self.slug = title.parameterize end

def notify_followers NotifyFollowersJob.perform_later(self) end

def clear_cache Rails.cache.delete("post/#{id}") end

def cleanup_attachments attachments.purge_later end

def log_save_time start = Time.current yield duration = Time.current - start Rails.logger.info "Post #{id} saved in #{duration}s" end end

  1. Enum Patterns

app/models/post.rb

class Post < ApplicationRecord

Basic enum

enum status: { draft: 0, published: 1, archived: 2 }

Enum with prefix/suffix

enum visibility: { public: 0, private: 1, unlisted: 2 }, _prefix: :visibility

Multiple enums

enum content_type: { article: 0, video: 1, podcast: 2 }, _suffix: :content

Scopes automatically created

Post.draft, Post.published, Post.archived

Post.visibility_public, Post.visibility_private

Post.article_content, Post.video_content

Query methods

post.draft?, post.published?, post.archived?

post.visibility_public?, post.visibility_private?

State transitions

def publish! published! if draft? end end

  1. Query Optimization

app/models/post.rb

class Post < ApplicationRecord

Eager loading to avoid N+1

scope :with_associations, -> { includes(:user, :tags, comments: :user) }

Select specific columns

scope :title_and_author, -> { select('posts.id, posts.title, users.name as author_name') .joins(:user) }

Batch processing

def self.process_in_batches find_each(batch_size: 1000) do |post| post.process end end

Pluck for arrays

def self.recent_titles order(created_at: :desc) .limit(10) .pluck(:title) end

Exists check (efficient)

def self.has_recent_posts?(user_id) where(user_id: user_id) .where('created_at > ?', 1.day.ago) .exists? end

Count with joins

def self.popular_authors joins(:user) .group('users.id', 'users.name') .select('users.id, users.name, COUNT(posts.id) as posts_count') .having('COUNT(posts.id) >= ?', 10) .order('posts_count DESC') end end

  1. Transactions

app/services/post_publisher.rb

class PostPublisher def self.publish(post, user) ActiveRecord::Base.transaction do post.update!(status: :published, published_at: Time.current) user.increment!(:posts_count) NotificationService.notify_followers(post)

  # If any operation fails, entire transaction is rolled back
end

rescue ActiveRecord::RecordInvalid => e Rails.logger.error "Failed to publish post: #{e.message}" false end

Nested transactions with savepoints

def self.complex_operation(post) ActiveRecord::Base.transaction do post.update!(featured: true)

  ActiveRecord::Base.transaction(requires_new: true) do
    # This creates a savepoint
    post.tags.create!(name: 'featured')
  end
end

end end

  1. STI (Single Table Inheritance)

app/models/vehicle.rb

class Vehicle < ApplicationRecord validates :make, :model, presence: true

def max_speed raise NotImplementedError end end

app/models/car.rb

class Car < Vehicle validates :doors, presence: true

def max_speed 120 end end

app/models/motorcycle.rb

class Motorcycle < Vehicle validates :engine_size, presence: true

def max_speed 180 end end

Usage

car = Car.create(make: 'Toyota', model: 'Camry', doors: 4) car.type # => "Car" Vehicle.all # Returns both cars and motorcycles Car.all # Returns only cars

  1. Concerns

app/models/concerns/sluggable.rb

module Sluggable extend ActiveSupport::Concern

included do before_validation :generate_slug validates :slug, presence: true, uniqueness: true end

class_methods do def find_by_slug(slug) find_by(slug: slug) end end

private

def generate_slug return if slug.present? base_slug = title.parameterize self.slug = unique_slug(base_slug) end

def unique_slug(base_slug) slug_candidate = base_slug counter = 1

while self.class.exists?(slug: slug_candidate)
  slug_candidate = "#{base_slug}-#{counter}"
  counter += 1
end

slug_candidate

end end

app/models/post.rb

class Post < ApplicationRecord include Sluggable end

Best Practices

  • Use scopes for reusable queries - Keep query logic in the model

  • Eager load associations - Prevent N+1 queries with includes/preload

  • Add database indexes - Index foreign keys and frequently queried columns

  • Use counter caches - Optimize count queries for associations

  • Validate at model level - Ensure data integrity with validations

  • Keep callbacks simple - Extract complex logic to service objects

  • Use transactions - Ensure data consistency for multi-step operations

  • Leverage concerns - Share common behavior across models

  • Use enums for state - Type-safe state management with enums

  • Write efficient queries - Use select, pluck, and exists appropriately

Common Pitfalls

  • N+1 queries - Forgetting to eager load associations

  • Callback hell - Too many callbacks making flow hard to follow

  • Fat models - Putting too much business logic in models

  • Missing indexes - Slow queries due to unindexed columns

  • Unsafe updates - Not using transactions for related operations

  • Validation bypass - Using update_attribute or save(validate: false)

  • Memory bloat - Loading all records instead of batching

  • SQL injection - Using string interpolation in where clauses

  • Counter cache mismatches - Manual updates breaking counter caches

  • Ignoring database constraints - Not adding DB-level validations

When to Use

  • Building data-backed Rails applications

  • Implementing business logic tied to database models

  • Creating REST APIs with Rails

  • Developing CRUD interfaces

  • Managing complex data relationships

  • Building multi-tenant applications

  • Creating admin interfaces with Active Admin

  • Implementing soft deletes and audit trails

  • Building reporting and analytics features

  • Creating content management systems

Resources

  • Active Record Basics Guide

  • Active Record Associations

  • Active Record Validations

  • Active Record Callbacks

  • Active Record Query Interface

  • Rails API Documentation

  • The Rails Way Book

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review