rails-model-generator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRails Model Generator (TDD Approach)
Rails 模型生成器(TDD 方法)
Overview
概述
This skill creates models the TDD way:
- Define requirements (attributes, validations, associations)
- Write model spec with expected behavior (RED)
- Create factory for test data
- Generate migration
- Implement model to pass specs (GREEN)
- Refactor if needed
本技能采用TDD方式创建模型:
- 定义需求(属性、验证、关联)
- 编写符合预期行为的模型测试用例(RED阶段:测试失败)
- 创建测试数据工厂
- 生成迁移文件
- 实现模型以通过测试(GREEN阶段:测试通过)
- 按需重构
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
undefinedModel: [ModelName]
模型:[ModelName]
Table: [table_name]
数据表:[table_name]
Attributes
属性
| Name | Type | Constraints | Default |
|---|---|---|---|
| name | string | required, unique | - |
| string | required, unique, email format | - | |
| status | integer | enum | 0 (pending) |
| organization_id | bigint | foreign key | - |
| 名称 | 类型 | 约束 | 默认值 |
|---|---|---|---|
| name | string | 必填、唯一 | - |
| string | 必填、唯一、邮箱格式 | - | |
| status | integer | 枚举 | 0(待处理) |
| organization_id | bigint | 外键 | - |
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
undefinedStep 2: Create Model Spec
步骤2:创建模型测试用例
Location:
spec/models/[model_name]_spec.rbruby
undefined路径:
spec/models/[model_name]_spec.rbruby
undefinedfrozen_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)
endend
=== 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')
endend
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)
endend
=== 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')
endend
end
查看 [templates/model_spec.erb](templates/model_spec.erb) 获取完整模板。Step 3: Create Factory
步骤3:创建工厂
Location:
spec/factories/[model_name_plural].rbruby
undefined路径:
spec/factories/[model_name_plural].rbruby
undefinedfrozen_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
endend
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
endend
end
查看 [templates/factory.erb](templates/factory.erb) 获取完整模板。Step 4: Run Spec (Verify RED)
步骤4:运行测试用例(验证RED阶段)
bash
bundle exec rspec spec/models/model_name_spec.rbExpected: 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:referencesReview the generated migration and add:
- Null constraints:
null: false - Defaults:
default: 0 - Indexes:
add_index :table, :column
ruby
undefinedbash
bin/rails generate migration CreateModelNames \
name:string \
email:string:uniq \
status:integer \
organization:references查看生成的迁移文件并添加:
- 非空约束:
null: false - 默认值:
default: 0 - 索引:
add_index :table, :column
ruby
undefineddb/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, :statusend
end
undefinedclass 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, :statusend
end
undefinedStep 6: Run Migration
步骤6:执行迁移
bash
bin/rails db:migrateVerify with:
bash
bin/rails db:migrate:statusbash
bin/rails db:migrate验证迁移状态:
bash
bin/rails db:migrate:statusStep 7: Create Model File
步骤7:创建模型文件
Location:
app/models/[model_name].rbruby
undefined路径:
app/models/[model_name].rbruby
undefinedfrozen_string_literal: true
frozen_string_literal: true
class ModelName < ApplicationRecord
end
undefinedclass ModelName < ApplicationRecord
end
undefinedStep 8: Run Spec (Still RED)
步骤8:运行测试用例(仍处于RED阶段)
bash
bundle exec rspec spec/models/model_name_spec.rbExpected: Failures for missing validations/associations.
bash
bundle exec rspec spec/models/model_name_spec.rb预期结果:因缺少验证/关联逻辑导致测试失败。
Step 9: Add Validations & Associations
步骤9:添加验证与关联关系
ruby
undefinedruby
undefinedfrozen_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
undefinedclass 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
undefinedStep 10: Run Spec (GREEN)
步骤10:运行测试用例(GREEN阶段)
bash
bundle exec rspec spec/models/model_name_spec.rbAll 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: trueruby
belongs_to :commentable, polymorphic: trueCounter Cache
计数器缓存
ruby
belongs_to :organization, counter_cache: trueruby
belongs_to :organization, counter_cache: trueAdd: organization.posts_count column
需添加:organization.posts_count 字段
undefinedundefinedSoft Delete
软删除
ruby
scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }
def soft_delete
update(deleted_at: Time.current)
endruby
scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }
def soft_delete
update(deleted_at: Time.current)
end