【Firebase】ストレージと連携

#firestore の設定
続いて、firebase のストレージと連携して、追加や削除を行ったリストやカードを保存できるようにしましょう。

まずは firebase のコンソール上でストレージを使うように、設定していきます。

コンソール画面のサイドバーの Database から、Firestore のページを開き、データベースの作成をクリックしてください。

![halkdejh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/57e17757-5eb2-5be6-86da-ebac40b47876.png)

ロケーションは、「asia-northeast1」を選択してください。
![pAtfU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/7fe15640-8295-9e3a-8919-e1ccd206ce91.png)

作成できたら、上にあるルールタブをクリックしてください。

![8QmBg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/9da96a77-1ce0-aeb8-ab6a-09c51fbd40e1.png)

この画面で、データベースへの読み書き可能なルールを設定することができます。 Cloud store では、パスの形式で、保存するデータの場所を指定できます。 今回はユーザーごとに、データを保存するので、以下のような階層でデータを保存します。

```
/users/{userId}/lists/{listsId}
```

/usersの下にユーザーごとの階層を作り、その下にリストのデータを保持する、/lists/の階層を作りたいと思います。

ルールで定義すると、以下のようになります。

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId}/lists/{listId} {
      allow read, update, delete: if request.auth.uid == userId;
      allow create: if request.auth.uid != null;
    }
  }
}
```

以下の部分が、データベースへのパスを表しています。

match /users/{userId}/lists/{listId}

{userId}と、{listId}には、それぞれユーザーの ID と、リストの ID が入ります。

allow read, update, delete: if request.auth.uid == userId;の部分が、read(作成), update(更新), delete(削除)のルールを定義しており、リクエストされたユーザー ID と、パスのユーザー ID が同じかどうかを確かめています。

つまり、自身のアカウントではないと、これらの操作はできません。

次のallow create: if request.auth.uid != null;では、認証済みの Google アカウントであれば、データの create(作成)ができるようになっています。

上記のルールを貼り付けて、ルールの作成をクリックしてください。

![CRSQ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/43d11a1d-49bd-547a-e988-7ac8f83c159c.png)

#リストの作成機能
続いて、データを Firestore に保存する機能を実装していきます。

まずは、リストを作成時のデータを保存できるようにします。

データの保存は、/users/{userId}/lists/{listId}としてルールを決めているので、まずは、ユーザー ID の取得をしなければいけません。

ユーザー ID を取得するゲッターを定義していきます。

```javascript:store/index.js
(省略)

// 追加
getters: {
  uid(state) {
    return state.loginUser ? state.loginUser.uid : null
  }
}

(省略)
```

続いて、addListの処理で、Firebase へデータを保存できるようにします。

```javascript:store/index.js
(省略)

mutations: {

  (省略)

  // 変更
  addList(state, payload) {
    state.lists.push(payload)
  },

  (省略)

actions: {

 (省略)

  // 変更
  addList({ getters,  commit }, title) {
    firebase.firestore().collection(`users/${getters.uid}/lists/`).add({title: title, cards: []}).then(function(res) {
      commit('addList', {id: res.id, title: title, cards: []})
    })
  },

(省略)
```

collectionメソッドの引数には、保存するパスを入れ、addメソッドで保存するデータを入れることができます。

collection メソッドの引数が、コンソール上で設定したパスと違うと、データの追加ができないので注意してください。

thenメソッドに渡した関数の引数に、add の結果を受け取ることができます。resとは response(レスポンス)の略です。

addメソッドだと、リスト ID が自動で生成されるので、store にも同じ ID を保存するために、生成された ID を、ミューテーションを呼び出す引数に入れています。

store に記述しているリストの配列を空にしてから、試しに新しいリストを作成してみましょう。

```javascript:store/index.js
(省略)

  state: {
    loginUser: null,
    lists: [] // 変更
  },

(省略)
```
![6386365287724ef81b18663cb1adba1a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/a15de726-2046-0c1a-1201-4f1f2d441451.gif)

追加できたら、firestore にも保存されているか、コンソールから確認しましょう。

以下のように firestore のコンソール上でも、作成を確認できるかと思います。
![6c6a6a9da9e8d6a42351743ed708f37e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/9da7d75d-35e6-c615-ed0c-e253364307a9.gif)

ただ、追加する処理はあっても、取得する処理はまだないので、リロードすると元に戻るかと思います。

#リストの削除機能
続いて、リストの削除機能を作成していきます。

リストの削除には、firestore 上でどのリストを削除するのか判別するため、アクションにリスト ID を渡す必要があります。

まず、List コンポーネントで、リスト ID をアクションに渡すように修正しましょう。

```vuejs:List.vue
(省略)

  methods: {
    removeList() {
      if (confirm("リストを削除しますか？")) {
        this.$store.dispatch("removeList", {
          listIndex: this.listIndex,
          listId: this.list.id // 追加
        });
      }
    },
  },

(省略)
```

続いて、firestore のリストを削除する処理をアクションに追加していきます

removeListアクションを以下のようにします。

```javascript:store/index.js
(省略)

actions: {

  (省略)

  removeList({ getters, commit }, payload) {
    firebase.firestore().collection(`users/${getters.uid}/lists/`).doc(payload.listId).delete().then(function() {
      commit('removeList', payload.listIndex)
    })
  },

(省略)
```

リストの追加と少し似ていますが、docメソッドの引数に、削除したいリストの ID を入れて、deleteメソッドで、リストを削除しています。

試しにリストの削除をしてみましょう。

![7b85b3fecee922d2bc1beaf4620a3a6e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/1cb573cc-1947-0ef9-11c5-85e880d80d3f.gif)

コンソールからでもリストの削除を確認できるかと思います。

#リストの取得
続いて、リストの取得を実装していきます。

リストの取得は、ログイン時の処理と同じタイミングで行うようにします。

まずは、firestore からデータを取得する処理を追加します。

```javascript:store/index.js
(省略)

actions: {

  (省略)

  // 追加
  fetchLists({ getters, commit}) {
    firebase.firestore().collection(`users/${getters.uid}/lists`).get().then(function(res) {
      res.forEach(function(doc) {
        commit('addList', { id: doc.id, title: doc.data().title, cards: doc.data().cards })
      })
    })
},

(省略)
```

get メソッドではデータの取得が行えます。 then メソッドに渡した関数の引数で、get の結果を受け取ることができます。

resという変数で結果を受け取り、foreEach メソッドで一つ一つのデータを store に格納しています。

これで、データを取得できる処理はできたので、これをログインの処理に組み込んでいきましょう。

```javascript:App.vue
(省略)

created() {
  firebase.auth().onAuthStateChanged(user => {
    if(user) {
      this.$store.dispatch('setLoginUser', user)
      this.$store.dispatch('fetchLists') // 追加
    } else {
      this.$store.dispatch('deleteLoginUser')
    }
  })
}

(省略)
```

これでデータの取得ができたので、リロードしても、データが保持されるようになりました。

![3a5ffd857f47a8ced5a2b3e75723f533.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/580ea9bc-c7e1-d88e-da41-ec9b74a0ae5c.gif)

#カードの作成機能
続いて、カードの作成機能を追加していきます。

カードの作成にも、どのリストか判別するために、リスト ID が必要になります。

まず CardCreate コンポーネントに、リスト ID を渡すために、List コンポーネントを修正していきます。


```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <Draggable
        :list="list.cards"
        animation="150"
        group="cards"
        @end="$emit('update')"
      >
        <Card
          v-for="(card, index) in list.cards"
          :title="card"
          :key="card.id"
          :listIndex="listIndex"
          :cardIndex="index"
        />
      </Draggable>
    </div>
    <!-- 変更 -->
    <CardCreate :listIndex="listIndex" :listId="list.id" />
  </div>
</template>

(省略)
```

続いて、渡した listId を CardCreate コンポーネントで受け取り、removeCard アクションに引数として渡します。

```vuejs:CardCreate.vue
(省略)

<script>
  export default {
    props: {
      listIndex: {
        type: Number,
        required: true,
      },
      // 追加
      listId: {
        type: String,
        required: true,
      },
    },
    data() {
      return {
        title: "",
        placeholder: "新しいカードを追加",
        active: false,
      };
    },
    methods: {
      addCard() {
        this.$store.dispatch("addCard", {
          listIndex: this.listIndex,
          title: this.title,
          listId: this.listId, // 追加
        });
        this.title = "";
      },
      focusIn() {
        this.active = true;
        this.placeholder = "カードのタイトルを入力...";
      },
      focusOut() {
        this.active = false;
        this.placeholder = "新しいカードを追加";
      },
    },
    computed: {
      bodyExists() {
        return this.title.length > 0;
      },
    },
  };
</script>

(省略)
```
続いて、addCard アクションを修正していきます。

```javascript:store/index.js
(省略)

actions: {

  (省略)

  addCard({ state, commit }, payload) {
    firebase.firestore().collection(`users/${state.loginUser.uid}/lists/`).doc(payload.listId).update({
      cards: firebase.firestore.FieldValue.arrayUnion(payload.title)
    }).then(() => {
      commit('addCard', payload)
    })
},

(省略)
```

doc メソッドで、カードを追加したいリストを読み取り、update メソッドで、リストの中の cards を更新しています。

firestore で配列にデータを追加するときは、push メソッドなどではなく、firebase.firestore.FieldValue.arrayUnion(payload.title)のように記述します。

これで、カードの作成が実装できたかと思います。

実際に firestore に保存されるのか確認してみましょう。

![Something went wrong](https://img-host.wemotion.co.jp/uploads/e55e8634255c9ec100627f1dcbe50f8e.gif)


#カードの削除機能
続いて、カードの削除機能を実装していきます。

push メソッドと同様に、firebase には、ネストされた配列に対して、splice メソッドなどが使えないので、削除する際は、「削除したい要素を除いた配列を用意し、その配列で丸々上書きする」

という処理が必要になります。

まずは、カードの作成と同様に、どのリストの配列を操作するのかの情報が必要になるので、リスト ID を Card コンポーネントで受け取れるようにしましょう。

List コンポーネントから、Card コンポーネントへリスト ID を受け渡します。

```vuejs:list.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <Draggable
        :list="list.cards"
        animation="150"
        group="cards"
        @end="$emit('update')"
      >
        <Card
          v-for="(card, index) in list.cards"
          :title="card"
          :key="card.id"
          :listIndex="listIndex"
          :cardIndex="index"
          :listId="list.id" <!-- 追加 -->
        />
      </Draggable>
    </div>
    <CardCreate :listIndex="listIndex" :listId="list.id" />
  </div>
</template>

(省略)
```

続いて、Card コンポーネントでリスト ID を受け取り、removeCard メソッドの引数に追加します。

```javascript:Card.vue
(省略)

<script>
  export default {
    props: {
      title: {
        type: String,
        required: true,
      },
      listIndex: {
        type: Number,
        required: true,
      },
      cardIndex: {
        type: Number,
        required: true,
      },
      // 追加
      listId: {
        type: String,
        required: true,
      },
    },
    methods: {
      removeCard() {
        if (confirm("カードを削除しますか？")) {
          this.$store.dispatch("removeCard", {
            listIndex: this.listIndex,
            cardIndex: this.cardIndex,
            listId: this.listId, // 追加
          });
        }
      },
    },
  };
</script>

(省略)
```

続いて、removeCard アクションの処理を記述します。

```javascript:store/index.js
(省略)

removeCard({ state, commit }, payload) {
  const newCards = state.lists[payload.listIndex].cards.filter(( _ , index) => {
    return index != payload.cardIndex
  })
  firebase.firestore().collection(`users/${state.loginUser.uid}/lists/`).doc(payload.listId).update({
    cards: newCards
  }).then(() => {
    commit('removeCard', payload)
  })
},

(省略)
```

以下の部分で、削除したいカードを除いた配列を作成しています。

filter メソッドは、条件にあった要素のみを抽出し、新しく配列を生成できます。

```javascript
const newCards = state.lists[payload.listIndex].cards.filter(function( _ , index) {
  return index != payload.cardIndex;
});
```

続いて以下の部分では、新しく作成した配列を、updateメソッドを用いて、上書きしています。

```javascript
firebase.firestore().collection(`users/${state.loginUser.uid}/lists/`).doc(payload.listId).update({
  cards: newCards
}).then(() => {
  commit('removeCard', payload)
})
```

配列が削除できるか確認してみましょう。

以下のように、firestoreでも削除を確認できれば大丈夫です。

![762a9c7452b490e040ba7ff4715b077f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/225c7982-9944-5361-bd8b-0df15c77d122.gif)

#ドラッグ&ドロップした時のリストの更新
リストの更新も、firebaseでは、配列を扱うように、丸々上書きすることはできません。

全てのリストを順番に上書きしていく作業が必要になります。

updateListアクションを修正しましょう。

```javascript:store/index.js
(省略)

updateList({getters, commit }, payload) {
  firebase.firestore().collection(`users/${getters.uid}/lists/`).get().then(res => {
    let index = 0
    res.forEach((doc) => {
      doc.ref.update({
        title: payload.lists[index].title,
        cards: payload.lists[index].cards
      })
      index++
    })
  })
  commit('updateList', payload)
},

(省略)
```

まずは、以下の部分でgetメソッドを使い、自身のユーザーの全てのリストを取得します。

resには、取得したデータが配列のような形で入っています。(正確にはquerysnapshotというオブジェクトになっています。)

firebase.firestore().collection(`users/${getters.uid}/lists/`).get().then(function(res) {
続いて、以下の部分で、取得したデータをforeEachメソッドを使い、全てに対してpayloadで取得したリストで更新を行います。

```javascript
let index = 0
res.forEach((doc) => {
  doc.ref.update({
    title: payload.lists[index].title,
    cards: payload.lists[index].cards
  })
  index++
})
```

以上で、ドラッグ&ドロップをしてもfirebaseに反映されるようになりました。

一旦動作を確認してみましょう。以下のようにドラッグ&ドロップした後にリロードしても、ちゃんと更新されているかと思います。