LoginSignup
3
1

More than 5 years have passed since last update.

Fluent でカスタムマイグレーションを作る方法

Posted at

これは「Vapor Advent Calendar 2018」9日目の投稿です。

はじめに

前回の投稿では、「本番運用するアプリでモデルの自動マイグレーションを使ってはいけない」ということと、その代わりに公式ドキュメントも推奨するカスタムマイグレーションを使うべきということを書きました。

今回は、その「カスタムマイグレーション」の具体的な作り方を書いていきたいと思います。

スキーマを作成するカスタムマイグレーションを作成する

モデルの自動マイグレーションはテーブルを CREATE (or DROP) しスキーマを作成するものでした。まず、それと同じ働きをするカスタムマイグレーションを作ってみましょう。

まず、以下の Article モデルがあるとします (簡略化のため、前回のモデルとはプロパティなど変更しています) 。

Article.swift
import FluentMySQL

final class Article: MySQLModel {
    var id: Int?
    var title: String?
}

このとき、テーブルを CREATE するマイグレーション (つまりこの時点ではモデルの自動マイグレーションと同等のもの) をカスタムマイグレーションとして作ると以下のようになります。

CreateArticle.swift
import FluentMySQL

struct CreateArticle: MySQLMigration {
    static func prepare(on conn: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.create(Article.self, on: conn) { builder in
            builder.field(for: \.id, isIdentifier: true)
            builder.field(for: \.title)
        }
    }

    static func revert(on connection: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.delete(Article.self, on: connection)
    }
}

モデルの自動マイグレーションの場合は Migration プロトコルに適合させましたが、カスタムマイグレーションでは <データベース名>Migration プロトコルを利用します。もちろん、上記で MySQL* となっている箇所は別のデータベースを利用する場合はそれに応じて変更されます。

サンプルコードに書いたように、実装すべきメソッドは preparerevert の2つです。知らなくても想像がつくと思いますが、マイグレーションを実行した際に prepare が実行され、revert するときに revert が実行されます。また、データベース上のデータ型は、自動マイグレーションと同様にモデルのプロパティから自動的に決定されます (指定することも可能です) 。実装に関しての詳細はドキュメントなどを参照してください。

モデルの自動マイグレーションと同様に、実行したいマイグレーションは登録しておく必要があります。

configure.swift
...

var migrations = MigrationConfig()
migrations.add(migration: CreateArticle.self, database: .mysql)
services.register(migrations)

...

ポイントは、add メソッドの引数で migration を指定することです。これで、vapor run などを実行したときにカスタムマイグレーションが実行されるようになります。

このようにカスタムマイグレーションとして実装することで、マイグレーションをモデルの設計に暗黙的に依存するのではなく、独立して明示的に扱えるようになります。

スキーマを更新するカスタムマイグレーション

モデルの自動マイグレーションには、テーブルを ALTER しスキーマを更新することができないという制限があります。カスタムマイグレーションを使うとスキーマの更新が可能です。

先ほどの Article モデルに article_url というカラムを追加するカスタムマイグレーションは以下のように書きます。

Article.swift
import FluentMySQL

final class Article: MySQLModel {
    var id: Int?
    var title: String?
    var article_url: String? // 追加
}
AddArticleArticleId.swift
import FluentMySQL

struct AddArticleArticleId: MySQLMigration {
    static func prepare(on conn: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.update(Article.self, on: conn) { builder in
            builder.field(for: \.article_id)
        }
    }

    static func revert(on conn: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.update(Article.self, on: conn) { builder in
            builder.deleteField(for: \.article_id)
        }
    }
}

データベース操作の API が create から update などと変わっただけで、全体の作りとしてはスキーマを作成するカスタムマイグレーションと同様ですね。

当然、このマイグレーションも登録しておく必要があります。

configure.swift
...

var migrations = MigrationConfig()
migrations.add(migration: CreateArticle.self, database: .mysql)
migrations.add(migration: AddArticleArticleId, database: .mysql)
services.register(migrations)

...

これでスキーマ更新もマイグレーションとして管理することが可能になります。

まとめ

モデルのカスタムマイグレーションは手軽で便利です。実際、開発中は積極的に活用していくべきでしょう。開発中であればモデルの設計も頻繁に変わるでしょうし、重要なデータが入っているわけでもないでしょうから、スキーマが変わったら単純にデータベースを作り直してしまえばよいので。

しかし、本番運用となるとそうはいきませんので、実際にリリースするアプリケーションでは、ここで挙げたようなカスタムマイグレーションを利用するようにするのが良いと考えています。

Vapor のコア開発者である Tanner も同様の発言をしています。

you implement separate migrations from the get-go. Being explicit is very advantageous IMO.

Ruby on Rails などの他のメジャー Web Application Framework を使っていた人なら、明示的にマイグレーションファイルを用意する方が自然に感じるでしょうし、ケースバイケースで効率的に使い分けていければよいですね。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1