Migration
Migration 是 ORM 用來紀錄資料庫表格異動的機制,隨著專案的演化,資料庫 schema 也必然需要被變更,ORM 可以紀錄每次資料庫的異動,這樣的機制就叫 migration,當代的 web 框架都有搭配各自的 ORM 也都有這樣的機制。Masonite 搭配的 ORM 是 Orator,有了 ORM,資料庫的 schema 異動都可以透過自己熟悉的框架語言去實現,不用碰 SQL 語法,…當然這只是理想的狀況,在決定欄位型態前還是得要了解後端資料庫的資料型態特性,或者是某些特殊的查詢一定要用 SQL 來寫,總之真實世界比理想狀況複雜的多。
建立 Migration 檔
情境:既有 users table,要幫每個 user 紀錄交易資料,故需要增加另一個 transactions table,並且具有關聯性。上面的描述以 ORM 的角度應該用 class 來敘述較好,但這篇文主要是談 migration 本身故不多談 class。
Craft 再次出動:
$ craft migraton create_transactions_table --create=transaction
Created migration: 2019_07_01_142116_create_transactions_table.py
create_transactions_table
:這次 migration 的名稱而已。--create=transactions
:表示這次是要新建 table。
和 Rails 不同的是,craft migration
不會拿後面的參數自己去幫我們新增檔案的內容,在此後面的參數命名不會影響到產出檔案的內容,只有被明確指示的 --create=transactions
這段敘述才會被引用進檔案內容。
定義欄位
可以看到它幫我們建了一個檔案,此檔案位在 databases/migrations/ 內,內容:
from orator.migrations import Migration
class CreateTransactionsTable(Migration):
def up(self):
"""
Run the migrations.
"""
with self.schema.create('transactions') as table:
table.increments('id')
table.timestamps()
def down(self):
"""
Revert the migrations.
"""
self.schema.drop('transactions')
欄位資料型態的定義參閱 Orator 的〈Schema Builder〉文件。
把要加的欄位定義進去:
table.date('date')
table.integer('amount')
table.string('receipt_number').nullable()
table.string('description').nullable()
.nullable()
表示此欄位接受空值。
加上與 users 的外鍵欄位:
table.integer('user_id').unsigned()
table.foreign('user_id').references('id').on('users')
外鍵要求資料型態要同原本的欄位,亦即 transactions.user_id
與 users.id
兩者資料型態要一致。因為 users.id
有 unsigned 的屬性,所以 transactions.user_id
也必須是 unsigned
的屬性。
事實上用 craft migration
做出來定義檔都會預帶一個 id
作為主鍵的敘述,像這樣:
table.increments('id')
,這邊的 increments()
都有加上 unsigned 的屬性。(好像是從 Laravel 抄來的)(其實整套都是抄 Laravel 的)
最後完整的檔案長這樣:
from orator.migrations import Migration
class CreateTransactionsTable(Migration):
def up(self):
"""
Run the migrations.
"""
with self.schema.create('transactions') as table:
table.increments('id')
table.integer('user_id').unsigned()
table.foreign('user_id').references('id').on('users')
table.date('date')
table.integer('amount')
table.string('receipt_number').nullable()
table.string('description').nullable()
table.timestamps()
def down(self):
"""
Revert the migrations.
"""
self.schema.drop('transactions')
跑 Migrate
定義都寫完,可以跑 migrate 了:
$ craft migrate
[OK] Migrated 2019_07_01_142116_create_transactions_table
這行指令不用進 database/migrations/ 裡面也可以跑。
最後看到 OK 就好了。
確認 Migration 狀況
多人開發的專案會遇到別人加了個 migration 自己卻沒注意到的狀況,可以用 craft migrate:status
來確認一下:
$ craft migrate:status
It took 0.33ms to execute the query ("SELECT * FROM sqlite_master WHERE type = 'table' AND name = ?", ['migrations'])
It took 0.09ms to execute the query ('SELECT "migration" FROM "migrations"', [])
+---------------------------------------------+------+
| Migration | Ran? |
+---------------------------------------------+------+
| 2018_01_09_043202_create_users_table | Yes |
| 2019_07_01_142116_create_transactions_table | Yes |
+---------------------------------------------+------+