Get the FREE Ultimate OpenClaw Setup Guide →

rails-models

npx machina-cli add skill Shoebtamboli/rails_claude_skills/rails-models --openclaw
Files (1)
SKILL.md
8.4 KB

Rails Models (ActiveRecord)

Quick Reference

PatternExample
Model Generationrails g model User name:string email:string
Migrationrails g migration AddAgeToUsers age:integer
Validationvalidates :email, presence: true, uniqueness: true
Associationhas_many :posts, dependent: :destroy
Callbackbefore_save :normalize_email
Scopescope :active, -> { where(active: true) }
QueryUser.where(active: true).order(created_at: :desc)

Model Definition

class User < ApplicationRecord
  # Constants
  ROLES = %w[admin user guest].freeze
  
  # Associations
  has_many :posts, dependent: :destroy
  has_many :comments
  belongs_to :organization, optional: true
  
  # Validations
  validates :email, presence: true, uniqueness: true
  validates :name, presence: true, length: { minimum: 2 }
  validates :role, inclusion: { in: ROLES }
  
  # Callbacks
  before_save :normalize_email
  after_create :send_welcome_email
  
  # Scopes
  scope :active, -> { where(active: true) }
  scope :recent, -> { order(created_at: :desc) }
  
  # Class methods
  def self.search(query)
    where("name ILIKE ?", "%#{query}%")
  end
  
  # Instance methods
  def full_name
    "#{first_name} #{last_name}"
  end
  
  private
  
  def normalize_email
    self.email = email.downcase.strip
  end
end

Migrations

Creating Tables

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false
      t.boolean :active, default: true
      t.integer :role, default: 0
      t.references :organization, foreign_key: true
      
      t.timestamps
    end
    
    add_index :users, :email, unique: true
  end
end

Modifying Tables

class AddFieldsToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :bio, :text
    add_column :users, :avatar_url, :string
    add_reference :users, :manager, foreign_key: { to_table: :users }
    
    change_column_null :users, :email, false
    change_column_default :users, :active, from: nil, to: true
  end
end

Validations

class User < ApplicationRecord
  # Presence
  validates :email, presence: true
  
  # Uniqueness
  validates :email, uniqueness: { case_sensitive: false }
  validates :username, uniqueness: { scope: :organization_id }
  
  # Format
  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :phone, format: { with: /\A\d{10}\z/ }
  
  # Length
  validates :name, length: { minimum: 2, maximum: 50 }
  validates :bio, length: { maximum: 500 }
  
  # Numericality
  validates :age, numericality: { greater_than: 0, less_than: 150 }
  
  # Inclusion/Exclusion
  validates :role, inclusion: { in: ROLES }
  validates :username, exclusion: { in: %w[admin root] }
  
  # Custom validation
  validate :email_domain_allowed
  
  private
  
  def email_domain_allowed
    return if email.blank?
    domain = email.split('@').last
    unless %w[example.com company.com].include?(domain)
      errors.add(:email, "must be from an allowed domain")
    end
  end
end

Associations

# One-to-Many
class Author < ApplicationRecord
  has_many :books, dependent: :destroy
  has_many :published_books, -> { where(published: true) }, class_name: 'Book'
end

class Book < ApplicationRecord
  belongs_to :author
end

# Many-to-Many (has_and_belongs_to_many)
class Student < ApplicationRecord
  has_and_belongs_to_many :courses
end

class Course < ApplicationRecord
  has_and_belongs_to_many :students
end

# Many-to-Many (has_many :through)
class Student < ApplicationRecord
  has_many :enrollments
  has_many :courses, through: :enrollments
end

class Enrollment < ApplicationRecord
  belongs_to :student
  belongs_to :course
end

class Course < ApplicationRecord
  has_many :enrollments
  has_many :students, through: :enrollments
end

# One-to-One
class User < ApplicationRecord
  has_one :profile, dependent: :destroy
end

class Profile < ApplicationRecord
  belongs_to :user
end

# Polymorphic
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end

class Post < ApplicationRecord
  has_many :comments, as: :commentable
end

class Photo < ApplicationRecord
  has_many :comments, as: :commentable
end

Callbacks

class User < ApplicationRecord
  # Order of execution:
  before_validation :normalize_data
  after_validation :log_validation_errors
  
  before_save :encrypt_password
  around_save :log_save_time
  after_save :clear_cache
  
  before_create :set_default_role
  after_create :send_welcome_email
  
  before_update :check_changes
  after_update :notify_changes
  
  before_destroy :check_dependencies
  after_destroy :cleanup_files
  
  private
  
  def normalize_data
    self.email = email.downcase if email.present?
  end
  
  def around_save
    start_time = Time.current
    yield
    Rails.logger.info "Save took #{Time.current - start_time}s"
  end
end

Scopes and Queries

class Post < ApplicationRecord
  # Scopes
  scope :published, -> { where(published: true) }
  scope :recent, -> { order(created_at: :desc) }
  scope :by_author, ->(author_id) { where(author_id: author_id) }
  scope :created_between, ->(start_date, end_date) {
    where(created_at: start_date..end_date)
  }
  
  # Chaining scopes
  # Post.published.recent.limit(10)
end

# Query methods
Post.where(published: true)
Post.where("views > ?", 100)
Post.where(author_id: [1, 2, 3])
Post.where.not(category: 'draft')

# Ordering
Post.order(created_at: :desc)
Post.order(views: :desc, created_at: :asc)

# Limiting
Post.limit(10)
Post.offset(20).limit(10)

# Joins
Post.joins(:author)
Post.joins(:author, :comments)
Post.left_joins(:comments)

# Includes (eager loading)
Post.includes(:author, :comments)
Post.includes(author: :profile)

# Selecting specific fields
Post.select(:id, :title, :created_at)
Post.pluck(:title)
Post.pluck(:id, :title)

# Aggregations
Post.count
Post.average(:views)
Post.maximum(:views)
Post.minimum(:views)
Post.sum(:views)

# Group
Post.group(:category).count
Post.group(:author_id).average(:views)

Enums

class Post < ApplicationRecord
  enum status: {
    draft: 0,
    published: 1,
    archived: 2
  }
  
  # Or with prefix/suffix
  enum visibility: {
    public: 0,
    private: 1
  }, _prefix: :visibility
  
  # Usage:
  # post.draft!
  # post.published?
  # Post.published
  # post.visibility_public!
end

Model Concerns

# app/models/concerns/taggable.rb
module Taggable
  extend ActiveSupport::Concern
  
  included do
    has_many :taggings, as: :taggable
    has_many :tags, through: :taggings
  end
  
  class_methods do
    def tagged_with(tag_name)
      joins(:tags).where(tags: { name: tag_name })
    end
  end
  
  def tag_list
    tags.pluck(:name).join(', ')
  end
end

# Usage in model
class Post < ApplicationRecord
  include Taggable
end

Best Practices

  1. Fat models, skinny controllers - Business logic belongs in models
  2. Use scopes for common queries
  3. Validate at database level with constraints when possible
  4. Use indexes for frequently queried columns
  5. Eager load associations to avoid N+1 queries
  6. Use concerns to share behavior across models
  7. Keep callbacks simple - avoid complex logic
  8. Use transactions for multi-step operations
  9. Avoid callbacks for cross-cutting concerns - use service objects instead

Common Pitfalls

  • N+1 queries: Use includes, preload, or eager_load
  • Callback hell: Keep callbacks simple, use service objects for complex logic
  • Mass assignment vulnerabilities: Use strong parameters in controllers
  • Missing indexes: Add indexes for foreign keys and frequently queried columns
  • Ignoring database constraints: Add NOT NULL, unique constraints in migrations

References

Source

git clone https://github.com/Shoebtamboli/rails_claude_skills/blob/main/lib/generators/claude/skills_library/rails-models/SKILL.mdView on GitHub

Overview

Rails Models (ActiveRecord) define how data is stored, validated, and related. This skill covers model definitions, migrations, validations, associations, and callbacks, with practical patterns shown in the SKILL.md examples.

How This Skill Works

Models inherit from ApplicationRecord and declare associations (has_many, belongs_to), validations (presence, format, uniqueness), and callbacks (before_save, after_create). Migrations create and modify tables, add indexes, and enforce constraints. Scopes and class methods provide reusable queries, like active or search patterns shown in the reference.

When to Use It

  • Model a user-like entity with name, email, active flag, and role to demonstrate validations and associations.
  • Create and evolve database tables with migrations (create_table, add_column, references) and maintain indexes.
  • Enforce data integrity with validations (presence, uniqueness, format, length, numericality) and custom validators.
  • Model relationships using has_many, belongs_to, and has_many :through or has_and_belongs_to_many, including dependent behavior.
  • Apply callbacks (before_save, after_create) for side effects like email normalization or welcome messages.

Quick Start

  1. Step 1: rails g model User name:string email:string
  2. Step 2: Define validations, associations, and callbacks in app/models/user.rb
  3. Step 3: Run rails db:migrate and test with rails console

Best Practices

  • Place presence, format, and uniqueness validations close to the fields they guard; leverage ROLES/SCOPE patterns where relevant.
  • Add a unique index on critical fields (e.g., email) to complement validations and improve lookups.
  • Use appropriate associations with dependent options (e.g., has_many :posts, dependent: :destroy) and through relationships for complexity.
  • Encapsulate business logic in class methods or scopes rather than controllers or views.
  • Apply callbacks judiciously and keep them fast and testable (e.g., normalize_email in before_save).

Example Use Cases

  • User model with has_many :posts, validates :email, presence and uniqueness.
  • Migration CreateUsers with name, email, active flag, role, organization reference and a unique index on email.
  • Validation rules for email format and phone number, plus length restrictions on names and bios.
  • Associations: Author has_many :books, Book belongs_to :author; and many-to-many examples with has_and_belongs_to_many or has_many :through.
  • Callbacks: before_save :normalize_email and after_create :send_welcome_email as shown in the sample.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers