rails-model-generator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rails Model Generator (TDD Approach)

Rails 模型生成器(TDD 方法)

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
本技能采用TDD方式创建模型:
  1. 定义需求(属性、验证、关联)
  2. 编写符合预期行为的模型测试用例(RED阶段:测试失败)
  3. 创建测试数据工厂
  4. 生成迁移文件
  5. 实现模型以通过测试(GREEN阶段:测试通过)
  6. 按需重构

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)
模型创建进度:
- [ ] 步骤1:定义需求(属性、验证、关联)
- [ ] 步骤2:创建模型测试用例(RED阶段)
- [ ] 步骤3:创建工厂
- [ ] 步骤4:运行测试用例(应失败 - 无模型/表)
- [ ] 步骤5:生成迁移文件
- [ ] 步骤6:执行迁移
- [ ] 步骤7:创建模型文件(空文件)
- [ ] 步骤8:运行测试用例(应失败 - 无验证逻辑)
- [ ] 步骤9:添加验证与关联关系
- [ ] 步骤10:运行测试用例(GREEN阶段)

Step 1: Requirements Template

步骤1:需求模板

Before writing code, define the model:
markdown
undefined
编写代码前,先定义模型:
markdown
undefined

Model: [ModelName]

模型:[ModelName]

Table: [table_name]

数据表:[table_name]

Attributes

属性

NameTypeConstraintsDefault
namestringrequired, unique-
emailstringrequired, unique, email format-
statusintegerenum0 (pending)
organization_idbigintforeign key-
名称类型约束默认值
namestring必填、唯一-
emailstring必填、唯一、邮箱格式-
statusinteger枚举0(待处理)
organization_idbigint外键-

Associations

关联关系

  • belongs_to :organization
  • has_many :posts, dependent: :destroy
  • has_one :profile, dependent: :destroy
  • 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
  • name: 存在性、唯一性、长度(最大100)
  • email: 存在性、唯一性、邮箱格式
  • status: 属于枚举值范围

Scopes

作用域

  • active: status = active
  • recent: ordered by created_at desc
  • by_organization(org): where organization_id = org.id
  • active: status = active
  • recent: 按created_at倒序排列
  • by_organization(org): where organization_id = org.id

Instance Methods

实例方法

  • full_name: combines first_name and last_name
  • active?: checks if status is active
  • full_name: 拼接first_name和last_name
  • active?: 检查status是否为active

Callbacks

回调

  • before_save :normalize_email
  • after_create :send_welcome_email
undefined
  • before_save :normalize_email
  • after_create :send_welcome_email
undefined

Step 2: Create Model Spec

步骤2:创建模型测试用例

Location:
spec/models/[model_name]_spec.rb
ruby
undefined
路径:
spec/models/[model_name]_spec.rb
ruby
undefined

frozen_string_literal: true

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

查看 [templates/model_spec.erb](templates/model_spec.erb) 获取完整模板。

Step 3: Create Factory

步骤3:创建工厂

Location:
spec/factories/[model_name_plural].rb
ruby
undefined
路径:
spec/factories/[model_name_plural].rb
ruby
undefined

frozen_string_literal: true

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

查看 [templates/factory.erb](templates/factory.erb) 获取完整模板。

Step 4: Run Spec (Verify RED)

步骤4:运行测试用例(验证RED阶段)

bash
bundle exec rspec spec/models/model_name_spec.rb
Expected: Failure because model/table doesn't exist.
bash
bundle exec rspec spec/models/model_name_spec.rb
预期结果:测试失败,因为模型/数据表不存在。

Step 5: Generate Migration

步骤5:生成迁移文件

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
undefined
bash
bin/rails generate migration CreateModelNames \
  name:string \
  email:string:uniq \
  status:integer \
  organization:references
查看生成的迁移文件并添加:
  • 非空约束:
    null: false
  • 默认值:
    default: 0
  • 索引:
    add_index :table, :column
ruby
undefined

db/migrate/YYYYMMDDHHMMSS_create_model_names.rb

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

Step 6: Run Migration

步骤6:执行迁移

bash
bin/rails db:migrate
Verify with:
bash
bin/rails db:migrate:status
bash
bin/rails db:migrate
验证迁移状态:
bash
bin/rails db:migrate:status

Step 7: Create Model File

步骤7:创建模型文件

Location:
app/models/[model_name].rb
ruby
undefined
路径:
app/models/[model_name].rb
ruby
undefined

frozen_string_literal: true

frozen_string_literal: true

class ModelName < ApplicationRecord end
undefined
class ModelName < ApplicationRecord end
undefined

Step 8: Run Spec (Still RED)

步骤8:运行测试用例(仍处于RED阶段)

bash
bundle exec rspec spec/models/model_name_spec.rb
Expected: Failures for missing validations/associations.
bash
bundle exec rspec spec/models/model_name_spec.rb
预期结果:因缺少验证/关联逻辑导致测试失败。

Step 9: Add Validations & Associations

步骤9:添加验证与关联关系

ruby
undefined
ruby
undefined

frozen_string_literal: true

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

Step 10: Run Spec (GREEN)

步骤10:运行测试用例(GREEN阶段)

bash
bundle exec rspec spec/models/model_name_spec.rb
All specs should pass.
bash
bundle exec rspec spec/models/model_name_spec.rb
所有测试用例应通过。

References

参考资料

  • See templates/model_spec.erb for spec template
  • See templates/factory.erb for factory template
  • See reference/validations.md for validation patterns
  • 查看 templates/model_spec.erb 获取测试用例模板
  • 查看 templates/factory.erb 获取工厂模板
  • 查看 reference/validations.md 获取验证模式

Common Patterns

常见模式

Enum with Validation

带验证的枚举

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

Polymorphic Association

多态关联

ruby
belongs_to :commentable, polymorphic: true
ruby
belongs_to :commentable, polymorphic: true

Counter Cache

计数器缓存

ruby
belongs_to :organization, counter_cache: true
ruby
belongs_to :organization, counter_cache: true

Add: organization.posts_count column

需添加:organization.posts_count 字段

undefined
undefined

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
ruby
scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }

def soft_delete
  update(deleted_at: Time.current)
end