Migration 是繼承 ActiveRecord::Migration 的 Ruby 類,用 DSL 描述數據庫結構變更,實現:
| 方法 | 用途 | 可逆 |
|---|---|---|
create_table | 創建表 | ✅ |
drop_table | 刪除表 | ✅ (需指定塊) |
add_column | 添加列 | ✅ |
remove_column | 刪除列 | ❌ (需在 down 指定類型) |
change_column | 修改列類型/默認值 | ❌ |
rename_column | 重命名列 | ✅ |
add_index | 添加索引 | ✅ |
remove_index | 刪除索引 | ✅ |
add_timestamps | 添加 created_at/updated_at | ✅ |
change_table | 批量修改表 | ✅ |
class AddFlagToProduct < ActiveRecord::Migration
def up
add_column :products, :flag, :boolean
Product.update_all(flag: false)
end
def down
remove_column :products, :flag
end
end
class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :boolean, default: false, null: false
# update_all 不可逆,不能寫在 change 中
end
end
change 只支持可逆操作(見上表)。數據變更、不可逆 DDL 必須用 up/down。
# 模型級(自動生成 create_table) rails g model Product name:string description:text # 獨立遷移 rails g migration AddPartNumberToProducts part_number:string rails g migration RemovePartNumberFromProducts part_number:string # 自動生成 remove_column rails g migration AddDetailsToProducts part_number:string price:decimal
文件名:YYYYMMDDHHMMSS_migration_name.rb,時間戳保證順序唯一。
# 運行所有未執行遷移 rake db:migrate # 指定版本(向上/向下) rake db:migrate VERSION=20120101000000 # 回滾最後 1 次 rake db:rollback # 回滾最後 3 次 rake db:rollback STEP=3 # 重做最後 3 次(回滾 + 重跑) rake db:migrate:redo STEP=3 # 重置數據庫(drop + create + migrate) rake db:reset
# 遷移 1
class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :boolean
Product.update_all(flag: false) # 使用當前代碼的 Product 模型
end
end
# 遷移 2(同事稍後添加)
class AddFuzzToProduct < ActiveRecord::Migration
def change
add_column :products, :fuzz, :string
Product.update_all(fuzz: 'fuzzy')
end
end
# Model 當前狀態
class Product < ActiveRecord::Base
validates :flag, :fuzz, presence: true
end
新同事拉取代碼執行 rake db:migrate:
Product 模型驗證要求 fuzz 存在 → 報錯 undefined method 'fuzz'。fuzz 列,模型緩存的列信息也不含 fuzz。
class AddFlagToProduct < ActiveRecord::Migration
# 定義遷移專用模型,隔離驗證
class Product < ActiveRecord::Base; end
def change
add_column :products, :flag, :boolean
Product.reset_column_information # 刷新緩存,識別新列
Product.update_all(flag: false)
end
end
class AddFuzzToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base; end
def change
add_column :products, :fuzz, :string
Product.reset_column_information
Product.update_all(fuzz: 'fuzzy')
end
end
原則:遷移中操作數據時,始終定義局部模型類,避免受應用代碼驗證/回調影響。
t.timestampscreate_table :products do |t| t.timestamps # 自動添加 created_at, updated_at (datetime, null: false) end change_table :products do |t| t.timestamps end
t.references(外鍵快捷方式)
create_table :products do |t|
t.references :category # 生成 category_id (bigint), 添加索引
end
# 多態引用
create_table :pictures do |t|
t.references :imageable, polymorphic: { default: 'Photo' }
# 生成 imageable_id, imageable_type (string, default: 'Photo')
end
suppress_messages / say / say_with_time 控制輸出
class CreateProducts < ActiveRecord::Migration
def change
suppress_messages do
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
say "Created products table"
suppress_messages { add_index :products, :name }
say "Added index!", true
say_with_time 'Waiting...' do
sleep 10
250
end
end
end
db/schema.rb:Ruby DSL 格式,默認輸出,跨數據庫可移植。db/structure.sql:數據庫原生 SQL(pg_dump/mysqldump),保留視圖、存儲過程、觸發器等高級對象。配置:config.active_record.schema_format = :sql 或 :ruby(默認)。
不要手動編輯 schema.rb,它是 rake db:migrate 自動生成的單一真實源。
add_column 默認值會鎖表重寫全表。add_index :table, :col, algorithm: :concurrently (PG) / LOCK=NONE (MySQL 8.0+)。Product.where(flag: nil).find_in_batches { |batch| batch.update_all(flag: false) }。Migration 是 Rails 數據庫演進的基石。關鍵實踐:
change,不可逆操作用 up/down。reset_column_information。references、timestamps 等輔助方法減少樣板代碼。schema.rb 為唯一真實源,嚴禁手改。「數據庫遷移如同飛機維修:必須在飛行中更換引擎,容不得半點差錯。」