2020.12.01

Firestoreのdocumentを取ってきたり書き込んだりする

今年はどこのアドカレにも参加していない(さみしい)のでただ書きます。<br> 2020年は業務でも個人開発でもFirebaseとFirestoreにたくさんお世話になりました。<br> その中でよくFirestoreにあるデータを一括で取ってきたり、一括で書き込んだりしたくなる場面が多かったのでそのやり方について書きます。

これができると何がうれしいの?

Firestoreとは

Cloud Firestore(以下Firestore)はGoogleが提供しているNoSQLのドキュメントデータベースです。<br>

うれしいポイント

FirestoreのUIは1つのドキュメントを読み書きするのは便利なのですが、複数のドキュメントを見比べたり編集したりするのは不便です。<br> データの取得でいうと一番簡単な例はデータ分析のときです。Analyticsで追い切れない内容を詳細に分析したいときに全ドキュメントが手元にほしい場面があります。<br> 書き込みでいうと例えばDB設計が変わって全ユーザに共通のプロパティを追加したいときに、ちゃんとやるならCloud Functionsを使ってバッチで書き込んだりしますが、個人開発だとそこまでするよりスクリプトを書いて終わらせてしまいたいなんてときもあります。<br> なのでデータを一括で取ってきたり書き込んだりできるとエンジニアもえらい人もうれしい。

実装

環境

好みによりPythonです。
firebase-adminというのを入れます。あとnullの判定で便利なのでPandasも使います。

Python 3.8.2
pandas 1.2.0
firebase-admin 4.5.0

データの取得

必要なものをimportして準備します。 credentialはFirebaseのプロジェクト設定のサービスアカウントから新しい秘密鍵を生成するとjsonファイルをダウンロードできるのでそのパスを渡します。

import pickle

import pandas as pd
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore

cred = credentials.Certificate(${credentialsのpath})
firebase_admin.initialize_app(cred)
db = firestore.client()

collectionにあるdocumentを一括で取得してdictにします。   subcollectionも取得します。<br>

// 任意のcollectionにあるdocumentを一括で取得する
def get_collection(db, COLLECTION_NAME, subcollections_list=[]):
    collection_document_dict = {}
    try:
        collection_ref = db.collection(COLLECTION_NAME)
    except:
        print("no collection named ", COLLECTION_NAME)
        return collection_document_dict

    for i, doc_ref in enumerate(collection_ref.list_documents()):
        doc = doc_ref.get()
        document_id = doc.id
        doc = doc.to_dict()
        if pd.isnull(doc):
            continue
        doc["document_id"] = document_id

        for subcollection in subcollections_list:
            doc[subcollection] = get_subcollection(doc_ref, subcollection)
        collection_document_dict[document_id] = doc
    return collection_document_dict

// documentにあるsubcollectionを取得する
def get_subcollection(doc_ref, SUBCOLLECTION_NAME):
    subcollection_document_list = []
    try:
        subcollection_ref = doc_ref.collection(SUBCOLLECTION_NAME)
    except:
        print("no subcollection named ", subcollection_name)
        return subcollection_document_list
    for sub_doc in subcollection_ref.stream():
        document_id = sub_doc.id
        sub_doc = sub_doc.to_dict()
        sub_doc["document_id"] = document_id
        subcollection_document_list.append(sub_doc)
    return subcollection_document_list

あとはこれをpickleで保存するなり pd.DataFrame(collection.values()) でDataFrameにして分析するなりできます。

データの書き込み

今度は書き込むときです。準備は取得と同じ。<br> 書き込むデータはdictで用意しておきます。<br>

// documentを新規作成する
db.collection(COLLECTION_NAME).document().set(data_dict)

// 既存のdocumentを編集する
collection = db.collection(COLLECTION_NAME).list_documents()
for doc in collection:
    doc.update({
        "new_field": NEW_VALUE
    })
    // subcollectionも編集する
    for subdoc in doc.collection(SUBCOLLECTION_NAME).stream():
        subdoc.update({
            "new_field_sub": NEW_VALUE_SUB
        })

<br> 削除するときは doc.update() のところを doc.delete() みたいな感じにするといけます。<br>

さいごに

Firestore便利なので今後も使っていきたい。<br> 分析が楽になると次は結果の受け渡しを楽にしたくなります。毎回csvで書き出して渡すよりも勝手にスプレッドシートに書き込んでURLを送るだけにしたい。そのうち書きます。