dchuk/rails_ai_agents · Archived

rails-model-generator

Creates Rails models using TDD approach - test first, then migration, then model. Use when creating new models, adding model validations, defining associations, or setting up database tables.

First seen Feb 13, 2026

Installation

$ npx skills add dchuk/rails_ai_agents --skill rails-model-generator

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from dchuk/rails_ai_agents · top by installs.

npx skills add dchuk/rails_ai_agents

Browse all from dchuk/rails_ai_agents

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Write, Edit, Bash, Glob, Grep

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,262 B
  • docs SUMMARY.md 220 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

Rails Model Generator (TDD Approach)

Overview

This skill creates models the TDD way:

  1. Define requirements (attributes, validations, associations)
  2. Write model test with expected behavior (RED)
  3. Create fixtures for test data
  4. Generate migration
  5. Implement model to pass tests (GREEN)
  6. Refactor if needed

Workflow Checklist

Model Creation Progress:
- [ ] Step 1: Define requirements (attributes, validations, associations)
- [ ] Step 2: Create model test (RED)
- [ ] Step 3: Create fixtures
- [ ] Step 4: Run test (should fail - no model/table)
- [ ] Step 5: Generate migration
- [ ] Step 6: Run migration
- [ ] Step 7: Create model file (empty)
- [ ] Step 8: Run test (should fail - no validations)
- [ ] Step 9: Add validations and associations
- [ ] Step 10: Run test (GREEN)

Step 1: Requirements Template

Before writing code, define the model:

## 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 Test

Location: test/models/[modelname]test.rb

# frozen_string_literal: true

require "test_helper"

class ModelNameTest < ActiveSupport::TestCase
  # === Associations ===
  test "belongs to organization" do
    model = model_names(:one)
    assert_respond_to model, :organization
    assert_instance_of Organization, model.organization
  end

  test "has many posts" do
    model = model_names(:one)
    assert_respond_to model, :posts
  end

  # === Validations ===
  test "requires name" do
    model = ModelName.new(name: nil)
    assert_not model.valid?
    assert_includes model.errors[:name], "can't be blank"
  end

  test "requires unique email (case insensitive)" do
    existing = model_names(:one)
    model = ModelName.new(email: existing.email.upcase)
    assert_not model.valid?
    assert_includes model.errors[:email], "has already been taken"
  end

  test "validates name length max 100" do
    model = ModelName.new(name: "a" * 101)
    assert_not model.valid?
    assert model.errors[:name].any? { |e| e.include?("too long") }
  end

  # === Scopes ===
  test ".active returns only active records" do
    active_record = model_names(:active_one)
    inactive_record = model_names(:inactive_one)

    results = ModelName.active
    assert_includes results, active_record
    assert_not_includes results, inactive_record
  end

  # === Instance Methods ===
  test "#full_name returns combined name" do
    model = ModelName.new(first_name: "John", last_name: "Doe")
    assert_equal "John Doe", model.full_name
  end
end

Step 3: Create Fixtures

Location: test/fixtures/[modelnameplural].yml

# test/fixtures/model_names.yml
one:
  name: "Test Model One"
  email: "[email protected]"
  status: 0
  organization: one

two:
  name: "Test Model Two"
  email: "[email protected]"
  status: 0
  organization: one

active_one:
  name: "Active Model"
  email: "[email protected]"
  status: 1
  organization: one

inactive_one:
  name: "Inactive Model"
  email: "[email protected]"
  status: 2
  organization: one

Step 4: Run Test (Verify RED)

bin/rails test test/models/model_name_test.rb

Expected: Failure because model/table doesn't exist.

Step 5: Generate Migration

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
# 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

bin/rails db:migrate

Verify with:

bin/rails db:migrate:status

Step 7: Create Model File

Location: app/models/[model_name].rb

# frozen_string_literal: true

class ModelName < ApplicationRecord
end

Step 8: Run Test (Still RED)

bin/rails test test/models/model_name_test.rb

Expected: Failures for missing validations/associations.

Step 9: Add Validations & Associations

# 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 Test (GREEN)

bin/rails test test/models/model_name_test.rb

All tests should pass.

References

  • See [reference/validations.md](reference/validations.md) for validation patterns

Common Patterns

Enum with Validation

enum :status, { draft: 0, published: 1, archived: 2 }
validates :status, inclusion: { in: statuses.keys }

Polymorphic Association

belongs_to :commentable, polymorphic: true

Counter Cache

belongs_to :organization, counter_cache: true
# Add: organization.posts_count column

Soft Delete

scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }

def soft_delete
  update(deleted_at: Time.current)
end

Normalizes (Rails 7.1+)

normalizes :email, with: -> { _1.strip.downcase }
normalizes :phone, with: -> { _1.gsub(/\D/, "") }