or run

tessl search
Log in

rails-model-generator

tessl install github:ThibautBaissac/rails_ai_agents --skill rails-model-generator

github.com/ThibautBaissac/rails_ai_agents

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.

Review Score

91%

Validation Score

13/16

Implementation Score

85%

Activation Score

100%

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:

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

# 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 for full template.

Step 3: Create Factory

Location: spec/factories/[model_name_plural].rb

# 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 for full template.

Step 4: Run Spec (Verify RED)

bundle exec rspec spec/models/model_name_spec.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 Spec (Still RED)

bundle exec rspec spec/models/model_name_spec.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 Spec (GREEN)

bundle exec rspec spec/models/model_name_spec.rb

All specs should pass.

References

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