【Swift】Realmのマイグレーションについて

# 目次
[マイグレーションとは](#マイグレーションとは)
[マイグレーションの実行](#マイグレーションの実行)
[プロパティの追加](#プロパティの追加)
[プロパティ名の変更](#プロパティ名の変更)
[プロパティの結合](#プロパティの結合)

# マイグレーションとは
マイグレーションとは一般的なデータベースの用語で、モデル定義を変更したときに、**保存されているデータを新しいモデル定義の仕様に移行する処理**のことを言います。

Realmの場合、以下のようにモデル定義を変更した場合、マイグレーションを行う必要があります。
```swift:Realm.swift
class Person: Object {
    dynamic var firstName = ""
    dynamic var lastName = ""
}
```
```swift:Realm.swift
class Person: Object {
    dynamic var fullName = ""
}
```

# マイグレーションの実行
マイグレーションの実行は、Realmの設定を行う際に扱うRealm.Configuratrion構造体にマイグレーションの設定を行い、デフォルトの設定であるRealm.Configuration.defaultCongigurationに代入します。その際、必ずschemaVersionの値を増加させる必要があります。

#### schemaVersion
schemaVesionとは、モデル定義の変更を管理するための内部バージョンのことを言います。schemaVersionはInt型であるため、整数のみしか設定できません。

モデル定義の変更において、オプショナルプロパティの追加もしくはプロパティの削除のみの場合は、マイグレーションは自動的に行われるためschemaVersionを増加させる設定のみで済みます。

```swift:Realm.swift
// 変更前
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var age = 0
}

// 変更後: ageを削除
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
}
```
```swift:AppDelegate.swift
// デフォルトでschemaVersionは0に設定されているため、１に増加。
let config = Realm.Configuration(schemaVersion: 1)

// 設定したものをdefaultConfigurationに代入。
Realm.Configuration.defaultConfiguration = config

// 初回のRealmインスタンスの生成で、マイグレーションが実行される。
let realm = try! Realm()
```

#### migrationBlock
以下のようなより複雑な変更を行う場合は、migrationBlockに移行処理の内容を記述する必要があります。

・初期値が必要なプロパティの追加
・プロパティ名の変更
・プロパティの結合

migrationBlockは、引数migrationにMigrationインスタンス、引数oldSchemaVersionにその時点でのschemaVersionが渡されるクロージャになっているので、これらを使用してデータベースに対する変更を行なっていきます。

# プロパティの追加
初期値が必要なプロパティを新たに追加する場合、migrationBlockでMigrationインスタンスのcreate(_: value:)メソッドを使用します。

```swift:Realm.swift
// 変更前
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
}

// 変更後: ageを追加
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var age = 0
}
```
```swift:AppDelegate.swift
let config = Realm.Configuration(
    schemaVersion: 2, // schemaVersionを1から2に増加。
    migrationBlock: { migration, oldSchemaVersion in
        // 設定前のschemaVersionが２より小さい場合、マイグレーションを実行。
        if oldSchemaVersion < 2 {
            // 第１引数にクラス名を、value引数に追加するプロパティを渡す。
            migration.create(Person.className(), value: ["age": 0])
        }
    })

Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()
```

# プロパティ名の変更
プロパティ名を変更する場合、migrationBlockでMigrationインスタンスのrenameProperty(onType: from: to:)メソッドを使用します。

```swift:Realm.swift
// 変更前
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var age = 0
}

// 変更後: ageをyearsSinceBirthに変更
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var yearsSinceBirth = 0
}
```
```swift:AppDelegate.swift
let config = Realm.Configuration(
    schemaVersion: 3, // schemaVersionを2から3に増加。
    migrationBlock: { migration, oldSchemaVersion in
        // 設定前のschemaVersionが3より小さい場合、マイグレーションを実行。
        if oldSchemaVersion < 3 {
            migration.renameProperty(onType: Person.className(), from: "age", to: "yearsSinceBirth")
        }
    })

Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()
```

# プロパティの結合
プロパティの結合を行う場合、migrationBlockでMigrationインスタンスのenumerateObjects(ofType: _:)メソッドを使用し、データベース内にあるモデルオブジェクトを取得します。次に取得した古いオブジェクトを使用して新しいオブジェクトを設定します。
```swift:Realm.swift
// 変更前
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var yearsSinceBirth = 0
}

// 変更後: firstNameとlastNameをfullNameに結合
class Person: Object {
    @objc dynamic var fullName = ""
    @objc dynamic var yearsSinceBirth = 0
}
```
```swift:AppDelegate.swift
let config = Realm.Configuration(
    schemaVersion: 4, // schemaVersionを3から4に増加
    migrationBlock: { migration, oldSchemaVersion in
        // のschemaVersionが4より小さい場合、マイグレーションを実行。
        if oldSchemaVersion < 4 {
            // データベース内にあるすべてのPersonモデルを列挙。
            migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in

                // 古いオブジェクトからfirstName,lastNameを取得。
                let firstName = oldObject!["firstName"] as? String
                let lastName = oldObject!["lastName"] as? String

                // 新しいオブジェクトのfullNameに、firstNameとlastNameを結合した値を設定。
                newObject!["fullName"] = "\(firstName!) \(lastName!)"
            }
        }

Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()
```



# 参考
・ MongoDBドキュメント
　 https://www.mongodb.com/docs/realm/sdk/swift/
・軽量・高速モバイルデータベース Realm入門 
　https://gihyo.jp/book/2017/978-4-7741-8848-5