Firebase

Firestoreの利用

Firestoreの設定

(1)Firebaseの画面で左側の「Cloud Firestore」をクリックします。表示された画面の「データベースの作成」をクリックします。

(2)「次へ」をクリックします。

(3)「有効にする」をクリックします。
なお、Storageを先に設定している場合はFirestoreのロケーションはStorageと同じ場所に保存されるようになり、変更はできません(逆にFirestoreを先に設定した場合もStorageのロケーションはFirestoreと同じになります)

firestoreモジュール

Firestoreの機能を利用するにはfirebaseパッケージのfirestoreモジュールを使います。ただし、firebaseパッケージをインポートするだけではfirestoreモジュールは使えず、別途firebase/firestoreパッケージもインポートする必要があります(firebase/firestoreをインポートせずにfirestoreモジュールを参照するとundefinedが返されます)

import firebase from 'firebase';
// firestoreを使う場合は別途インポートが必要 // 以下のように書くことでコードが実行されfirestoreモジュールが設定される
import 'firebase/firestore';
// firestoreモジュールですが「DB機能」という意味で「db」という名前の変数に代入します const db = firebase.firestore();
データの追加

データ(ドキュメント)を追加するためにはまずドキュメント追加対象のコレクションへの参照(CollectionReference)を取得します。

const itemsCollection = db.collection('items');

コレクションへの参照を取得したらaddメソッドを呼び出すことでドキュメントを追加できます。addメソッドはPromiseを返すので、thenメソッドに登録するコールバックで追加したドキュメント情報を受け取ることができます。
なお、ドキュメントを追加すると自動的にIDが割り振られます。IDは自分で割り振ることも可能です(その場合、もちろんIDが重複しないようにルールを決める必要があります)

    itemsCollection.add({name, price})
.then(doc => {
// IDを指定しない場合は自動的に割り振られる
console.log(doc.id);
});
データの取得

データを取得するにはCollectionReferenceオブジェクトのgetメソッドを使います。Promiseが返されるのでthenメソッドにコールバックを登録するとQuerySnapshotオブジェクトが渡されます。QuerySnapshotオブジェクトにはforEachメソッドが用意されており、一つ一つのドキュメント(DocumentSnapshotオブジェクト)が渡されるので以下の例のように配列に格納するなどしてプログラムから利用しやすいようにします。

    const items = [];
// コレクション内の全データを取得
itemsCollection.get()
.then(querySnapshot => {
// 取得を実行するとQuerySnapshotが渡される
// forEachメソッドを呼び出すことで個々のドキュメントを取得できる
querySnapshot.forEach(doc => {
// IDとデータは分けられているが使いやすいようにまとめる
items.push({id: doc.id, ...doc.data()});
});
this.setState({items});
});

データを並び替えて取得したり、条件に合うデータのみを取得したりすることもできます。データの並び替えを行うにはorderByメソッドを使用します。orderByメソッドを呼び出すとQueryオブジェクトが返されますが、Queryオブジェクトにもgetメソッドが定義されていますので、同じようにデータの取得処理を記述することが可能です。

    // ascは昇順(小さい順)、descを指定すると降順(大きい順)
    itemsCollection.orderBy('price', 'asc').get()
.then(querySnapshot => { // 省略
});

取得するデータの条件を指定するにはwhereメソッドを使います。whereメソッドもQueryオブジェクトを返します。

    // 第2引数で比較演算子を指定。<, <=, ==, >, >=が使える。!=は使えない
    itemsCollection.where('price', '<', 3000).get()
.then(querySnapshot => { // 省略
});
データの更新

データを更新するにはまずCollectionReferenceオブジェクトのdocメソッドを使って更新したいデータ(ドキュメント)のDocumentReferenceオブジェクトを取得します。DocumentReferenceオブジェクトのupdateメソッドを呼び出すことでデータの更新が行えます。以下の例では「全フィールド」を更新していますが、updateメソッドには「更新が必要なフィールド」だけを渡すことも可能です。
updateメソッドもPromiseを返し、コールバックを登録することで「正しく更新が行えた」ことを確認できます。

  update() {
// 更新するドキュメントの参照を取得(IDを指定)
const ref = itemsCollection.doc(this.props.item.id);
const name = this.state.name;
const price = parseInt(this.state.price);
ref.update({name, price});
}
データの削除

データの削除も更新と同様にDocumentReferenceオブジェクトを取得します。DocumentReferenceオブジェクトのdeleteメソッドを呼び出すことでデータが削除されます。

  delete() {
const ref = itemsCollection.doc(this.props.item.id);
ref.delete();
}
アクセス権限(ルール)

アクセス権限を設定するためには「権限の種類」「ドキュメントパスと変数」「リクエストに設定されている情報」について理解する必要があります。Storageのルールもほぼ同じです。

権限は以下のものがあります。

それぞれの権限について条件式を記述し、trueであればアクセス許可が与えられます。Firestoreのデフォルトは以下のようになっています。条件式がfalseなので「どのようなアクセスに対しても許可を与えない」という意味になります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

ユーザー情報(usersドキュメント)に対する許可設定を考えます。「読み込みは誰でも可能」という権限を設定する場合は常にtrueとすれば問題ありません。一方で「書き込み(内容の修正)は本人しかできない」という設定については以下のように設定します。

service cloud.firestore {
  match /databases/{database}/documents {
    // アクセス対象のドキュメントパスが「/users/abcd1234」のような場合にチェックされる
    match /users/{uid} {
      allow read: if true;
      // ドキュメントパスに含まれるuidとアクセス者のuidが一致する場合はtrue
      // request.authについては後述
      allow write: if request.auth.uid == uid;
    }
  }
}

ドキュメントパスの一部を{ }で囲むと対応部分が変数に設定され、条件式で利用できます。例では「リクエスト時に設定されている認証情報のuidとアクセス対象ドキュメントのパスの一部であるuidが一致すれば許可(条件式がtrueになる)」という意味になります。
なお、デフォルトで書かれている「match /{document=**}」は少し特殊な意味で「/で始まるドキュメント(つまり全ドキュメント)にルールを適用する」という意味になります。変数名documentの後ろの「=**」が「このパスで始まるもの全部」という意味を表します。

条件式を書く際には組み込み変数を利用できます。よく使うものは以下となります。

これらを用いることで投稿情報(photosドキュメント)に関する「作成(新規投稿)はログインしていれば可能、内容の修正・削除は本人しかできない」というルールを記述できます。なお、ドキュメント自体に「uid」というフィールドを設定しておく必要があります。

service cloud.firestore {
  match /databases/{database}/documents {
    match /photos/{pid} {
      allow read: if true;
      // ログインしていればuidにはnull以外が設定されている
      allow create: if request.auth.uid != null;
      // ドキュメント内のuid(データを作ったユーザー)と現在のuidが一致する場合のみ許可
      allow update, delete: if request.auth.uid == resource.data.uid;
    }
  }
}
インデックス

Firestoreでは複数のフィールドに対して条件を指定する場合にはインデックスが必要という点に注意が必要です(今回のように検索条件と並び替え条件で別のフィールドを使う場合も複数フィールドに該当します)。このインデックスはあらかじめ作成しておかないと検索時に例外が発生します。

インデックスは「インデックスがありません」という例外で表示されたURLにアクセスすることで作成できます。