home / skills / thibautbaissac / rails_ai_agents / rails-model-generator
/skills/rails-model-generator
This skill creates Rails models using a test-driven approach, guiding you from requirements to green specs for reliable, scalable data models.
npx playbooks add skill thibautbaissac/rails_ai_agents --skill rails-model-generatorReview the files below or copy the command above to add this skill to your agents.
---
name: rails-model-generator
description: Creates Rails models using TDD approach - spec first, then migration, then model. Use when creating new models, adding model validations, defining associations, or setting up database tables.
allowed-tools: Read, Write, Edit, Bash
---
# Rails Model Generator (TDD Approach)
## Overview
This skill creates models the TDD way:
1. Define requirements (attributes, validations, associations)
2. Write model spec with expected behavior (RED)
3. Create factory for test data
4. Generate migration
5. Implement model to pass specs (GREEN)
6. Refactor if needed
## Workflow Checklist
```
Model Creation Progress:
- [ ] Step 1: Define requirements (attributes, validations, associations)
- [ ] Step 2: Create model spec (RED)
- [ ] Step 3: Create factory
- [ ] Step 4: Run spec (should fail - no model/table)
- [ ] Step 5: Generate migration
- [ ] Step 6: Run migration
- [ ] Step 7: Create model file (empty)
- [ ] Step 8: Run spec (should fail - no validations)
- [ ] Step 9: Add validations and associations
- [ ] Step 10: Run spec (GREEN)
```
## Step 1: Requirements Template
Before writing code, define the model:
```markdown
## Model: [ModelName]
### Table: [table_name]
### Attributes
| Name | Type | Constraints | Default |
|------|------|-------------|---------|
| name | string | required, unique | - |
| email | string | required, unique, email format | - |
| status | integer | enum | 0 (pending) |
| organization_id | bigint | foreign key | - |
### Associations
- belongs_to :organization
- has_many :posts, dependent: :destroy
- has_one :profile, dependent: :destroy
### Validations
- name: presence, uniqueness, length(max: 100)
- email: presence, uniqueness, format(email)
- status: inclusion in enum values
### Scopes
- active: status = active
- recent: ordered by created_at desc
- by_organization(org): where organization_id = org.id
### Instance Methods
- full_name: combines first_name and last_name
- active?: checks if status is active
### Callbacks
- before_save :normalize_email
- after_create :send_welcome_email
```
## Step 2: Create Model Spec
Location: `spec/models/[model_name]_spec.rb`
```ruby
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ModelName, type: :model do
subject { build(:model_name) }
# === Associations ===
describe 'associations' do
it { is_expected.to belong_to(:organization) }
it { is_expected.to have_many(:posts).dependent(:destroy) }
end
# === Validations ===
describe 'validations' do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
it { is_expected.to validate_length_of(:name).is_at_most(100) }
end
# === Scopes ===
describe '.active' do
let!(:active_record) { create(:model_name, status: :active) }
let!(:inactive_record) { create(:model_name, status: :inactive) }
it 'returns only active records' do
expect(described_class.active).to include(active_record)
expect(described_class.active).not_to include(inactive_record)
end
end
# === Instance Methods ===
describe '#full_name' do
subject { build(:model_name, first_name: 'John', last_name: 'Doe') }
it 'returns combined name' do
expect(subject.full_name).to eq('John Doe')
end
end
end
```
See [templates/model_spec.erb](templates/model_spec.erb) for full template.
## Step 3: Create Factory
Location: `spec/factories/[model_name_plural].rb`
```ruby
# frozen_string_literal: true
FactoryBot.define do
factory :model_name do
sequence(:name) { |n| "Name #{n}" }
sequence(:email) { |n| "user#{n}@example.com" }
status { :pending }
association :organization
trait :active do
status { :active }
end
trait :with_posts do
after(:create) do |record|
create_list(:post, 3, model_name: record)
end
end
end
end
```
See [templates/factory.erb](templates/factory.erb) for full template.
## Step 4: Run Spec (Verify RED)
```bash
bundle exec rspec spec/models/model_name_spec.rb
```
Expected: Failure because model/table doesn't exist.
## Step 5: Generate Migration
```bash
bin/rails generate migration CreateModelNames \
name:string \
email:string:uniq \
status:integer \
organization:references
```
Review the generated migration and add:
- Null constraints: `null: false`
- Defaults: `default: 0`
- Indexes: `add_index :table, :column`
```ruby
# db/migrate/YYYYMMDDHHMMSS_create_model_names.rb
class CreateModelNames < ActiveRecord::Migration[8.0]
def change
create_table :model_names do |t|
t.string :name, null: false
t.string :email, null: false
t.integer :status, null: false, default: 0
t.references :organization, null: false, foreign_key: true
t.timestamps
end
add_index :model_names, :email, unique: true
add_index :model_names, :status
end
end
```
## Step 6: Run Migration
```bash
bin/rails db:migrate
```
Verify with:
```bash
bin/rails db:migrate:status
```
## Step 7: Create Model File
Location: `app/models/[model_name].rb`
```ruby
# frozen_string_literal: true
class ModelName < ApplicationRecord
end
```
## Step 8: Run Spec (Still RED)
```bash
bundle exec rspec spec/models/model_name_spec.rb
```
Expected: Failures for missing validations/associations.
## Step 9: Add Validations & Associations
```ruby
# frozen_string_literal: true
class ModelName < ApplicationRecord
# === Associations ===
belongs_to :organization
has_many :posts, dependent: :destroy
# === Enums ===
enum :status, { pending: 0, active: 1, suspended: 2 }
# === Validations ===
validates :name, presence: true,
uniqueness: true,
length: { maximum: 100 }
validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
# === Scopes ===
scope :active, -> { where(status: :active) }
scope :recent, -> { order(created_at: :desc) }
# === Instance Methods ===
def full_name
"#{first_name} #{last_name}".strip
end
end
```
## Step 10: Run Spec (GREEN)
```bash
bundle exec rspec spec/models/model_name_spec.rb
```
All specs should pass.
## References
- See [templates/model_spec.erb](templates/model_spec.erb) for spec template
- See [templates/factory.erb](templates/factory.erb) for factory template
- See [reference/validations.md](reference/validations.md) for validation patterns
## Common Patterns
### Enum with Validation
```ruby
enum :status, { draft: 0, published: 1, archived: 2 }
validates :status, inclusion: { in: statuses.keys }
```
### Polymorphic Association
```ruby
belongs_to :commentable, polymorphic: true
```
### Counter Cache
```ruby
belongs_to :organization, counter_cache: true
# Add: organization.posts_count column
```
### Soft Delete
```ruby
scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }
def soft_delete
update(deleted_at: Time.current)
end
```
This skill creates Rails models using a strict TDD workflow: write the spec first, create factories, generate migrations, implement the model, then refactor. It ensures models are delivered with tests, proper database schemas, validations, associations, scopes, and common patterns like enums and callbacks. The flow reduces regressions and keeps behavior specification central to development.
You define model requirements (attributes, associations, validations) then generate an RSpec model spec and a FactoryBot factory. Run specs to see failing tests, generate and run the migration, add the model file, then implement validations, associations, enums, scopes, and instance methods until specs pass. The skill includes templates and a checklist to enforce RED → GREEN → REFACTOR discipline.
What should I include in the initial requirements template?
List table name, attributes with types and constraints, associations, validations, scopes, instance methods, and callbacks. This drives both spec and migration generation.
When do I add indexes and null constraints?
Add indexes and null constraints when generating or editing the migration, before running migrations. Tests should fail first (no table), then you migrate and continue implementing model behavior.