rails-active-record-patterns
Use when active Record patterns including models, associations, queries, validations, and callbacks.
What this skill does
# 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
```bash
# 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
```yaml
# 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
```ruby
# 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
```
### 2. Associations
```ruby
# 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
```
### 3. Advanced Queries
```ruby
# 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)
```
### 4. Validations
```ruby
# 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
```
### 5. Callbacks
```ruby
# 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
```
### 6. Enum Patterns
```ruby
# 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
```
### 7. Query Optimization
```ruby
# 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
```
### 8. Transactions
```ruby
# 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
eRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.