Firestoreに保存されたデータを端末によって分ける方法

#はじめに
タイトルにも書いてある通り、Firestoreに保存されたデータを端末によって分ける方法です。

導入方法などは割愛します。

Firestore独自の用語が沢山出てくるのでよく分からない方は、こちらを読んでみて下さい。
[Cloud Firestoreについてまとめる](https://qiita.com/reo0612/items/cd78c026de5a2a8c2acb)

後はセキュリティルールについても。
[Firestoreのセキュリティルールとは](https://qiita.com/reo0612/items/aae7b4a35989a37f306a)

まあ、自分のQiitaの記事を紹介しているのになんですが、やはり公式ドキュメントが一番良きです。
[Cloud Firestore | Firebase](https://firebase.google.com/docs/firestore?hl=ja)

#書こうと思った理由
現在、開発しているアプリにて、お気に入り登録機能を追加しようと考えており
Firestoreで、そのデータを管理しようと思ったのが事の発端です。

データの読み書きはすんなり出来たものの、そのデータをユーザーの端末によって分けるというのがどうしても出来ませんでした。

どうにかメンターに相談したり自分なりに試行錯誤したのち、どうにか出来たので備忘録としてまとめようと思いました。

#やり方
###1.Firebase Authenticationの導入
まずは、[Firebase Authentication](https://firebase.google.com/docs/auth?hl=ja)をアプリに導入して下さい。

そして今回は`メールアドレス/パスワード`を使用したユーザー認証を導入したていで書きます。
(他の認証方法をあまり使ったことがない...)

導入して実際にAuthアカウントを作成すると、それぞれのユーザーに対して`uid`というユーザーIDが付与されます。
これはユーザーによってバラバラであり、重複していません。

この`uid`を使ってFirestoreに保存されたデータを端末ごとに分けていきます。

###2.FirestoreのドキュメントIDをuidで登録する
FirestoreのドキュメントIDは通常、追加と同時に`ランダムなID`が付与されます。

なのでこのドキュメントIDを先ほどの`uid`で登録することによってユーザーごとにデータを分けることが出来ます。

例えば、食べ物の名前をFirestoreに保存しようとします。

```swift
// Firestoreにデータを追加するメソッド
func addFoodData(userId: String, foodName: String, complition: ((Error?) -> Void)? = nil) {
        
    Firestore.Firestore().collection("users").document(userId).collection("food").addDocument(data: [
        "Name": foodName
            
    ]) { (error) in
        if let error = error {
            complition?(error)
            return
        }
    }
}
```
メソッドの`userId`引数から、`uid`を取得できるようにして
`.document(userId)`からFirestoreのドキュメントIDを`uid`で登録しました。

これでFirestoreにデータを保存するたびに、`uid(ドキュメントID)からユーザーを分けることができ、端末ごとにデータを分けることができます`。

上記のコードが保存されたら、こんな感じでFirestoreにて管理されます↓

```
🗂・・・コレクション、サブコレクション
📄・・・ドキュメント
ユーザーAのuid・・・123
ユーザーBのuid・・・456

・ユーザーAの場合
🗂users
  📄123
    🗂food
      📄自動生成したID
        Name: "Apple"

・ユーザーBの場合
🗂users
  📄456
    🗂food
      📄自動生成したID
        Name: "Sushi"
```
#ただこの方法でもいいが・・・
[Firestoreのセキュリティルール](https://firebase.google.com/docs/firestore/security/rules-structure?hl=ja)を書くときに少しだけ問題があります。

まずは公式が推奨しているセキュリティルールを見てください。
[安全でないルールを修正する | Firebase](https://firebase.google.com/docs/firestore/security/insecure-rules?hl=ja#content-owner-only)

見ていただけると分かるようにFirebase Authenticationと連携したセキュリティルールは、`認証済みユーザーが存在していてFirestoreに保存してあるuidとAuthアカウントのuidが一致していればデータの読み書きを許す`という感じだと思います。

`認証済みユーザーが存在していて`の部分は、Firebase Authenticationを導入していればOKですが`Firestoreに保存してあるuidとAuthアカウントのuidが一致していれば`の部分は現状、満たせていません。

なぜなら、あくまで今までの方法はドキュメントIDを`uid`で登録しているだけで`Firestoreにデータとして保存している訳ではないから`です。

#ではどうすればいいのか？
解決方法は簡単で、Firestoreに`uid`を保存すれば解決します。

先ほどの`Firestoreにデータを追加するメソッド`を少しだけ改修しましょう。

```swift
// Firestoreにデータを追加するメソッド
func addFoodData(userId: String, foodName: String, complition: ((Error?) -> Void)? = nil) {
    // uidをここで保存する
    Firestore.Firestore().collection("users").document(userId).setData([
            "userId": userId

      ]) { (error) in
          if let error = error {
             complition?(error)
             return
         }
    }
        
    Firestore.Firestore().collection("users").document(userId).collection("food").addDocument(data: [
        "Name": foodName
            
    ]) { (error) in
        if let error = error {
            complition?(error)
            return
        }
    }
}
```
上記のコードがFirestoreに保存されたら、こんな感じ↓

```
🗂・・・コレクション、サブコレクション
📄・・・ドキュメント
ユーザーAのuid・・・123
ユーザーBのuid・・・456

・ユーザーAの場合
🗂users
  📄123
    userId: 123
     🗂food
       📄自動生成したID
         Name: "Apple"

・ユーザーBの場合
🗂users
  📄456
    userId: 456
     🗂food
       📄自動生成したID
         Name: "Sushi"
```
これでやっと、セキュリティルール内でAuthアカウントの`uid`とFirestoreに保存した`userId`を比較することができますね。

セキュリティルールも設定していきましょう。

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
  
  function isAuth(userId) {
  	return request.auth != null && userId == request.auth.uid;
  }  
    match /users/{userId}/food/{document} {
    	allow write, read: if isAuth(userId);
    }
  }
}
```
これで、Firestoreに保存されたデータの安全性が保たれます。

#おわりに
最後のメソッドのコードはちゃんと動きますが、もしかすると
もっと良い書き方があるかもしれません。

詳しい方はコメントでアドバイスして頂けると有り難いです。