【Swift】避けてきたRealm migrationを学ぶ。

# こんにちは。いるべさんです。

### 経緯
アプリを作るとき、とてもお世話になるローカルデータベース。
リリース前の開発段階ではDBの構成を変えて不整合エラー出してもリセットして~~逃げて~~乗り越えてきた。

ただ、リリース後にそんなことしてユーザ様に「ローカルデータ捨ててインストールし直してくださいよw」なんて言えないのでコチラが譲歩する。(やれやれ。)

### ということで
その為にマイグレーションを行う。
書き方を知ればとても簡単なので、いるべさんはこの記事に残す。

# 準備！

### ネコちゃん

今回はネコちゃんのデータをこねくり回す。
最初は名前と年齢を持っておくだけ。
 
```swift:CatRealm.swift
class Cat: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0
}
```

### データ保存

ここは今回の本筋では無いのでさらーっと。

```swift:ViewController.swift
let realm = try! Realm()

let mofu = Cat()
mofu.name = "たぬ"
mofu.age = 5

try! realm.write() {
    realm.add(mofu)
}
```

うちにはタヌキみたいな見た目のモフモフしたネコがいる。
カワイイ。
「だから」たぬちゃんのデータを保存しておく。写真みたいなもんだ。


# リネームしてみる。

### ネコも家族。
まず変数名を変更してみる。
nameプロパティをfullNameに変更する。

```swift:CatRealm.swift
class Cat: Object {
    @objc dynamic var fullName = ""
    @objc dynamic var age = 0
}
```

この時点・この状態で実行してみる。

**もちろん不整合エラー**

↓↓↓↓↓解決策

### マイグレーション

マイグレーションを行う。

コードスラスラ読めないマンからすると「うっ！」となるコードですが、、
ほとんどこのまま流用できるので最初は脳死で使いましょう！！！

```swift:Realm.swift

func migration() {
    Realm.Configuration.defaultConfiguration = Realm.Configuration(
        schemaVersion: 1,　// ①
        migrationBlock: { migration, oldSchemaVersion in
            if(oldSchemaVersion < 1) {
                migration.renameProperty(onType: Cat.className(), from: "name", to: "fullName") //②
            }
       }
    })
}
```

大事なのは①schemaVersionと②migration.renamePropertyの行だけ。

- ①のschemaVersionは何も変更していなければ最初は0なので、初回変更時は1に設定する。
- 変更するたびに1ずつインクリメントすることでマイグレーションを行える。


- **schemaVersionはUInt型なので1.4みたいなバージョンは指定できない。**
- **例えばschemaVersionに一度例えば5を指定してマイグレーション後、3など５より下に指定するとエラー**

今回はnameをfullNameに変更するので
renamePropertyのfromに変更前のプロパティ名。
toに変更後のプロパティ名を入れれば変更できる。

```swift:ViewController.swift
let realm = try! Realm()
var mofu = realm.objects(Cat.self).first
try! realm.write {
    mofu.fullName = "いるべ" + mofu.fullName
}
```

### 確かめる。

**上のmigration関数をCatを読み出す前に実行させる。**
→ しないと不整合エラー

Catの情報を見てみると、

```
Results<Cat> <0x7f9ca7809210> (
	[0] Cat {
		fullName = "いるべたぬ";
		age = 5;
	}
)
```

たぬもこれで家族の一員だとハッキリする。


# プロパティを追加＆削除する。

### 種類も覚えておきたい。

ラグドールって聞いたことありますか？
あんまり聞かないよなぁと思うので種類のデータも保存しましょう！

あと、たぬは女の子なので年齢は隠します。(今更)

```swift:CatRealm.swift
class Cat: Object {
    @objc dynamic var fullName = ""
    @objc dynamic var kind = "ラグドール"
}
```


初期値にうちのたぬの種類：「ラグドール」を入れておく。
！実際にはこんなに具体的な初期値を入れるのはお勧めしません〜。


### マイグレーションする！

```swift:Realm.swift
Realm.Configuration.defaultConfiguration = Realm.Configuration(
    schemaVersion: 2,
    migrationBlock: { migration, oldSchemaVersion in
        if(oldSchemaVersion < 2) {
            migration.enumerateObjects(ofType: Cat.className()) { _, _ in
                // pass: 何もいらない
            }
        }
    }
})
```

**２回目の変更なので前回のschemaVersionに1プラスすることが大事。**

### 確かめる。
```
Results<Cat> <0x7f9ca7809210> (
	[0] Cat {
		fullName = "いるべたぬ";
		kind = "ラグドール";
	}
)
```

プロパティの追加、削除は**元のオブジェクトクラスにプロパティを追加してマイグレーションを行う**だけ。思ってたよりずっと簡単なんだなぁと思った。


# 前のデータを利用する。

kindが何となく邪魔になったので名前と合体させることにした。
もはやうちのたぬが可愛いだけの実用性のないオブジェクトになっていくのは目を瞑りましょう。

ﾀｽ､ｹﾃ､､

```swift:CatRealm.swift
class Cat: Object {
    @objc dynamic var nameAndKind = ""
}
```

実際にこんなプロパティは作らないでくださいというアンチパターン！！！
流石に誰もこんなことしないか。笑

```swift:Realm.swift
Realm.Configuration.defaultConfiguration = Realm.Configuration(
    schemaVersion: 3,
    migrationBlock: { migration, oldSchemaVersion in
        if(oldSchemaVersion < 3) {
            migration.enumerateObjects(ofType: Cat.className()) { old, new in
                new!["nameAndKind"] = old!["fullname"] + ":" + old!["kind"]
            }
        }
    }
})
```

migration.enumerateObjectsの、oldとnewについて

- oldは変更前のname, ageを持ったCatオブジェクト。
- newは変更後のfullName, ageを持ったCatオブジェクト。


これだけで新しいプロパティを追加できる。

### 確かめる。
```
Results<Cat> <0x7f9ca7809210> (
	[0] Cat {
		nameAndKind = "いるべたぬ:ラグドール";
	}
)
```
一個前の削除も同時に行われて救いようがない、もしくは救うのがめんどくさいオブジェクトができました！


# いるべさんと学ぶカンタンマイグレーションでした。
# ここまでお読み頂きありがとうございました。