Realmに初期データを入れてマスターとして使いたい

・モチベーション
　Realmにあらかじめデータを投入した状態で使用したかった

・前提
　id, value程度の単純なモデル

##今回の成果物

こちらに置いてあります
[negibouze/Realm-Register-Initial-Data](https://github.com/negibouze/Realm-Register-Initial-Data)

##1. プロジェクトにRealmを追加する

省略

##2. モデルを作成する

#### step1. 基底のクラスを作成する

```swift:DBBase.swift
import Foundation
import RealmSwift

/* 基底 */
class DBBase: Object {
    dynamic var id = 0
    dynamic var name = ""

    override static func primaryKey() -> String? {
        return "id"
    }

    override static func indexedProperties() -> [String] {
        return ["id"]
    }
}
```

#### step2. モデルを作成する

基本的なパターン

```swift:Prefecture.swift
import Foundation
import RealmSwift

/* 都道府県 */
class Prefecture: DBBase {
}
```

サブモデルを持っているパターン

```swift:Owner.swift
import Foundation
import RealmSwift

/* オーナー */
class Owner: DBBase {
    var dogs = List<Dog>()
}
```

##3. シード（データ）を作成する

#### step1. protocolを作成する

```swift:RealmSeed.swift
protocol RealmSeed {
    associatedtype SeedType: DBBase
    static var values: [[Any]] { get }
    static func items() -> [SeedType]
}

extension RealmSeed {
    static func items() -> [SeedType] {
        return values.map { val in
            let t = SeedType()
            t.id = val[0] as! Int
            t.name = val[1] as! String
            return t
        }
    }
}
```

#### step2. structを作成する

**！この方法だとデータ数が5,000件くらいで結構つらいです。**

```swift:PrefectureSeed.swift
struct PrefectureSeed: RealmSeed {
    typealias SeedType = Prefecture
    static var values: [[Any]] {
        return PrefectureData.data
    }
}

struct PrefectureData {
    static let data: [[Any]] = [
        [1, "北海道"],
        [2, "青森県"],
        [3, "岩手県"],
        [4, "宮城県"],
        （中略）
        [47, "沖縄県"]
    ]
}
```

```swift:OwnerSeed.swift
import RealmSwift

struct OwnerSeed: RealmSeed {
    typealias SeedType = Owner
    static var values: [[Any]] {
        return OwnerData.data
    }
}

extension OwnerSeed {
    static func items() -> [SeedType] {
        return values.map { val in
            let id = val[0] as! Int
            let t = SeedType()
            t.id = id
            t.name = val[1] as! String
            let l = { (_ ownerId: Int) -> [Dog] in
                let data = DogData.get(ownerId)
                return data.map { v in
                    let d = Dog()
                    d.id = v[0] as! Int
                    d.name = v[1] as! String
                    return d
                }
            }(id)
            t.dogs = List<Dog>(l)
            return t
        }
    }
}

struct OwnerData {
    static var data: [[Any]] {
        return [
            [1, "オーナー1"],
            [2, "オーナー2"],
            [3, "オーナー3"],
            [4, "オーナー4"],
            [5, "オーナー5"]
        ]
    }
}

struct DogData {
    static let data: [Int: [[Any]]] = [
        1: [
            [11, "オーナー1の犬1"],
            [12, "オーナー1の犬2"],
            [13, "オーナー1の犬3"],
            [14, "オーナー1の犬4"],
            [15, "オーナー1の犬5"]
        ],
        2: [
            [21, "オーナー2の犬1"],
            [22, "オーナー2の犬2"],
            [23, "オーナー2の犬3"]
        ]
        （以下略）
    ]
    
    static func get(_ ownerId: Int) -> [[Any]] {
        return data[ownerId]!
    }
}
```

##4. Initializerを作成する

```swift:RealmInitializer.swift
import Foundation
import RealmSwift

struct RealmInitializer {

    static func setUp() {
        // シードデータ作成
        insertSeedData(PrefectureSeed())
        insertSeedData(OwnerSeed())
        print(Realm.Configuration.defaultConfiguration.fileURL!)
    }

    private static func delete<T: RealmSeed>(_ seed: T) where T.SeedType: DBBase {
        // realm
        let realm = try! Realm()
        try! realm.write {
            realm.delete(realm.objects(T.SeedType.self))
        }
    }

    // Realmファイルを作成する
    private static func insertSeedData<T: RealmSeed>(_ seed: T) where T.SeedType: DBBase {
        // realm
        let realm = try! Realm()
        try! realm.write {
            T.items().forEach { val in
                realm.add(val, update: true)
            }
        }
    }
}
```

##5. ビルドする

```swift:AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // ここに追加
        RealmInitializer.setUp()
        return true
    }
    （中略）
}
```

##6. データ確認

コンソールに出力されたパスを開く

```
open /Users/...
```

default.realmをRealm Browserで開くと、こんな感じのデータが登録されています。

・prefecture
![prefecture.png](https://qiita-image-store.s3.amazonaws.com/0/37319/624c9120-2768-b1fb-3ee1-92afbb7609bb.png)

・owner
![owner.png](https://qiita-image-store.s3.amazonaws.com/0/37319/378ccd2b-d2a9-f7db-5c20-36d496769528.png)

・dog
![dog.png](https://qiita-image-store.s3.amazonaws.com/0/37319/0526f742-ea52-47ff-d984-bd10143ae34f.png)

##7. 作ったファイルを使う

#### step1. 対象プロジェクトにdefault.realmをコピーする

作成したデータを使いたいプロジェクトに6.のdefault.realmをコピーする

#### step2. AppDelegateに処理を追加する

```swift:AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // ここに追加
        copyRealm()
        return true
    }
    （中略）
}

extension AppDelegate {
    // Realmファイルをコピーする
    fileprivate func copyRealm() {
        let defaultRealmPath = Realm.Configuration.defaultConfiguration.fileURL!
        // 存在する場合は何もしない
        if FileManager.default.fileExists(atPath: defaultRealmPath.path) {
            return
        }
        let bundleRealmPath = Bundle.main.url(forResource: "default", withExtension: "realm")
        do {
            try FileManager.default.copyItem(at: bundleRealmPath!, to: defaultRealmPath)
        } catch let error {
            print("error copying realm file: \(error)")
        }
    }
}
```

##8. おまけ

事前データの用意でサブモデルのデータを紐づける際に（例だとOwnerとDog）、

```
[1, 11, "オーナー1の犬1"]
[1, 12, "オーナー1の犬2"]
[1, 13, "オーナー1の犬3"]
[1, 14, "オーナー1の犬4"]
[1, 15, "オーナー1の犬5"]
[2, 21, "オーナー2の犬1"]
[2, 22, "オーナー2の犬2"]
[2, 23, "オーナー2の犬3"]
```

こういうのを

```
[
    1: [
        [11, "オーナー1の犬1"],
        [12, "オーナー1の犬2"],
        [13, "オーナー1の犬3"],
        [14, "オーナー1の犬4"],
        [15, "オーナー1の犬5"]
    ],
    2: [
        [21, "オーナー2の犬1"],
        [22, "オーナー2の犬2"],
        [23, "オーナー2の犬3"]
    ]
]
```

こうしたかったので、[こういうの](https://codepen.io/negibouze/pen/aJQXjj)を用意しました。