Firebase Model Framework Saladaを使ってUser Modelを作る

# Firebase Model Framework Saladaを使ってUser Modelを作る
Firebaseについて実践的な内容を記事にしていないなと思い、普段私がアプリで採用してるUser Modelを紹介します。

__この記事が紹介すること__

- Saladaのインストール
- FirebaseでUser Modelを作る
- Firebase AuthとUser Modelを連携する

## Saladaのインストール
ついに**[Salada](https://github.com/1amageek/Salada)**がCococapodsに対応しました。
インストールには pod 1.4以上を使う必要があります。

まずcocoapodsをアップデートしましょう。
```
$ gem install cocoapods --pre
```

あとはいつも通りPodfileにこれを書くだけ
```
pod 'Salada'
```

これでインストールは完了です！
それぞれのファイルがどんな役割を持っているかについて詳しく聞きたい方は[Twiiter](https://twitter.com/1amageek)なり、Qiitaに質問ください。喜んで回答します。

## FirebaseでUser Modelを作る
早速User Modelを作って行きましょう。`User.swift`でもいいんですが、Realmで同じUser Modelを作った時に名前が衝突するのが嫌だったのでそれ以来私は`Firebase+User.swift`としています。Firebase Authにも`User`と定義されているクラスがあるので何かしら名前空間を与えてあげた方がいいと思います。

<img width="358" alt="スクリーンショット 2017-10-09 22.01.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/a070f0fa-689f-fd7a-f969-b3633d83e599.png">

今回はシンプルですがよく使いそうな構成を紹介します。

``` swift
import Firebase

extension Firebase {
    class User: Object {

        @objc dynamic var name: String?

        @objc dynamic var thumbnailImage: File?

        let followers: Follower = []

        let friends: Friend = []
    }
}
```

ユーザーの名前とサムネイルとフォロー機能を提供します。
`File`を利用すればFirebaseStorageとシームレスに連携できます。

``` swift
extension Firebase.User {

    static func current(_ completionHandler: @escaping ((Firebase.User?) -> Void)) {
        guard let user: User = Auth.auth().currentUser else {
            completionHandler(nil)
            return
        }
        Firebase.User.observeSingle(user.uid, eventType: .value, block: { (user) in
            guard let user: Firebase.User = user else {
                // FirebaseのDBにUserが保存されていない場合はログアウトする
                _ = try? Auth.auth().signOut()
                completionHandler(nil)
                return
            }
            completionHandler(user)
        })
    }

    // ユーザーをフォローする
    public func follow() {
        Firebase.User.current { (me) in
            guard let me = me else { return }
            self.followers.insert(me)
            me.friends.insert(self)
        }
    }

    // ユーザーをフォローをやめる
    public func unfollow() {
        Firebase.User.current { (me) in
            guard let me = me else { return }
            self.followers.remove(me)
            me.friends.remove(self)
        }
    }
}
```

Relationクラスを継承することで簡単に参照を作ることができます。

``` swift
extension Firebase {
    // フォローしている
    class Follower: Relation<Firebase.User> {

    }
}

extension Firebase {
    // フォローされている
    class Friend: Relation<Firebase.User> {

    }
}
```

### Userを保存する

``` swift
let user: Firebase.User = Firebase.User()
user.name = "hoge"
user.thumbnailImage = File(data: UIImageJPEGRepresentation(image, 1))
user.save()
```

### 自分のユーザーにアクセスする

``` swift
Firebase.User.current { user in
    // Do something
}
```

### 他のユーザーにアクセスする

``` swift
Firebase.User.observeSingle("id", eventType: .value) { (user) in
    // Do something      
}
```

### プロパティを更新

``` swift
Firebase.User.current { user in
    user.name = "Update"
}
```

SaladaはUpdateのファンクションを用意していません。
プロパティを監視して値がセットされたタイミングでリアルタイムにFirebaseに保存されるようになっています。
なので新しい値をセットしてあげるだけです。なぜこのようになっているかと言うとFirebaseはオフラインでも使えるからです。
オフライン時のエラーのハンドリングが不要な訳です。そもそもFirebaseではタイムアウトと言う概念がありませんしエラーも返ってきません。オフラインの場合は、一時的にローカルに保存され、ネットワークに再接続された瞬間にFirebase本体と同期します。

## Firebase AuthとUser Modelを連携する
実はこれが結構厄介で、Firebase Authは認証が完了すると`AuthStateDidChange`と言うNotificationを飛ばしてくれるんですが、実際欲しいNotificationは認証が完了したあとユーザー作成されてからの方が嬉しい訳です。

``` swift
Auth.auth().signIn(with: credential) { (authUser, error) in
    guard let authUser = authUser else {
        print(error)
        return
    }

    // AuthStateDidChangeはこの時点で飛んじゃう。
    Firebase.User.observeSingle(authUser.uid, eventType: .value) { user in
        if let user = user {
            // すでにユーザーがある場合はそのままログイン
        } else {
            // ない場合はユーザーを作る
            let newUser: Firebase.User = Firebase.User(id: authUser.uid)!
            newUser.name = authUser.displayName
            newUser.save { (_, error) in
                if let _ = error {
                    print(error)
                    return
                }
                // ここで自前のNotificationを飛ばしましょう。
            }
        }
    }
}
```

では、素敵なFirebase　Lifeを送ってください。