Firebase / Firestoreのインテグレーション

Firebaseデータの最上位に内部ツールを作成する方法について説明します。

RetoolはFirebaseのAdmin APIに対応しているため、Firestore、RealtimeDB、および認証データの最上位に高速かつ実用的なCRUDアプリを作成することができます。

Firebaseの設定

Retool内でFirebaseデータの最上位に作成するには、Firebaseの設定を接続する必要があります。Retoolアカウントを作成したら、Resourcesタブに移動して「create new」ボタンをクリックし、「APIs」セクションからFirebaseを選択します。

以下の情報が必要になります。

  1. FirebaseデータベースのURL

Firebaseコンソールに移動し、プロジェクトを選択します。左側のサイドバーのRealtime Databaseをクリックします。これでURLがhttps://you-project-id.firebaseio.com/の形で表示されます。これをRetoolにコピーします。

  1. FirestoreプロジェクトID

Firebaseコンソールのプロジェクト設定に戻り、プロジェクトIDをコピーします。プロジェクトIDは、retool-522x1のようになります。

  1. サービス・アカウント・キー

すでにFirebaseコンソールに入っている場合は(手順どおりに進めば入っているはずです)、プロジェクト設定パネルをクリックし、「service accounts」タブを選択し、「Firebase Admin SDK」アイコンをクリックしてから「Generate new private key」ボタンをクリックします。Retool内に直接ペーストできるJSON BLOBがダウンロードされます。

ダウンロードできない場合は、Google Cloud Platformコンソールでもアカウント・キーを作成できます。Google Cloud Platformコンソールに移動してプロジェクトを選択し、Create Service Accountをクリックし、名前を付けてから「role」でFirebase Adminを選択します。continueをクリックし、Create Keyをクリックして、JSONを選択します。ダウンロード後にRetoolへのコピーが可能になります。

設定は以下のようになります。

サービス・アカウント・キーとデータベースのURLを入力し、「Save」をクリックします。RetoolがFirebaseデータベースへ接続できることを確認します。これで準備完了です。

RealtimeDBの使用

データベースの照会

RealtimeDBを照会するには、新規クエリーを作成し(下部のパネルの+new)、Select resourceドロップダウンからFirebaseリソースを選択します。次に、Service typeドロップダウンから「Database (read / write)」を選択し、Action typeで「Query database」を選択します。

データベースの照会に必要なのはDatabase refだけです。設定ではsongsblogPostsについて最上位の参照を使用するため、これらのうちどちらかをクエリー内で使用できます。

「Use ordering」チェックボックスをクリックすると、他の順序付けやフィルタリングのオプションにアクセスできます。

これらのオプションすべてが実際に順序付けに関連しているわけではないので、少し分かりにくいと思います。すぐに修正する予定です。

データの設定と更新

Firebaseデータベースではデータの更新に2種類のAPIを使用します。

  1. .set()

.set()メソッドは、選択されたオブジェクトを入力された値と入れ替えます。例えば、オブジェクトに5つのフィールドがあり、.set()を使用して何かを3つのフィールドで渡すと、オブジェクトのフィールドが3つになります。Retoolでこのメソッドを使用するには、Action typeドロップダウンから「Set」を選択します。データベース参照と設定するオブジェクトを入力してください。

  1. .update()

.update()メソッドは、フィールドが存在すればフィールドの更新を行いますが、オブジェクト全体を入れ替えるわけではありません。クエリーのフォーマットは.set()と同じです。Action typeドロップダウンから「Update data」を選択し、データベース参照と更新するオブジェクトを入力します。

❗️

ルート・レベルのプロパティの設定や更新は行うことができません

Retoolではデータベースのルートで.update().set()のクエリーを実行することはできません。データベース参照に/が含まれていない場合(つまり、子を参照している場合)は、クエリーにエラーが発生します。

データベース参照を入力してもこのエラーが発生する場合は、先頭にスラッシュを付けてください(例: characters/charactersにする)。

フィールドの削除

Retoolではフィールドやオブジェクトを直接削除することはできませんが、set()メソッドを使用すると可能になります。.set()でプロパティを空白にすると、プロパティがデータベースから削除されます。nullは波括弧({{ }})で囲んだJavascriptを使用して渡す必要があることを覚えておいてください。例えば、songsオブジェクトから「title」プロパティを削除する場合は、以下のようになります。

Firestoreの使用

以下のすべての例ではFirestoreを使用しますが、RealtimeDBを使用している場合も同じ考えが適用されます。FirebaseアカウントをRetoolに接続したら(前のセクションを参照)、すぐにFirestoreを照会できるようになります。

📘

Firestore Admin Panelテンプレート

Firestoreデータ上での基本的なCRUDを検討している皆さまのために、取り掛かりやすいテンプレートを作成しました。詳細についてはこちらを参照してください。あるいは、Retoolのホームページで「create new」をクリックして「create from template」をクリックしてください。Firestore Admin Panelテンプレートを探して作成しましょう。

Firestoreの照会とコレクションの取得

Firestoreを照会するには、新規クエリーを作成し(下部のパネルの+new)、Service typeドロップダウンから「Firestore」を選択してAction typeで「Query Firestore」を選択します。コレクションをドロップダウンで指定するか、「Use raw id」をクリックしてカスタム値を入力します。

RetoolのGUIから、キー(ドキュメント・フィールド)、演算子(==in>=など)、および値を使用して照会することができます。文を必要な数だけ追加したり、制限やフィールドごとの順序、他にも面白いものを追加したりすることができます。

キーや値は、JavaScriptで動的に設定して検索などに使用することができます。TextInputコンポーネントがある場合は、そのコンポーネントの値を「value」の入力に渡すことができます。ここでは、ny-facilitiesコレクションにおける「Boro」フィールドの値をユーザーが入力できるTextInputコンポーネントを追加して、そのコンポーネントの値をFirestoreクエリーの「value」フィールドに渡しています。これで検索します。

コレクション内のすべてのドキュメントを俯瞰的に見るために、このFirestoreクエリーの結果をTableコンポーネントに取り込むことができます。これは、コレクションのフォーマット方法のおかげで単にテーブルの「data」フィールドの{{ listDocuments.data }}を参照するだけで行うことができます。しかし、Firestoreのデータの編成が異なる場合があります。

🚧

Firestoreのクエリーの結果をTableコンポーネント用にフォーマットする場合

Firestoreクエリーの結果は、すぐにはテーブル内での表示に適したフォーマットにならない場合があります。Retool内ではどこでもJavaScriptを記述できるのでご安心ください。

テーブル内にデータを表示させるために、以下のようなコードを使用したくなると思います。

{{ _.values(firebaseQuery.data) }}

さらには、以下のようにして、Firebaseキーを取り込みたい場合もあると思います。

_.zipWith(_.keys(firebaseQuery.data), _.values(firebaseQuery.data), (key, value) => Object.assign({}, { key: key}, value))

テーブルの「data」フィールドにこのような実際のコードを書き込んで、クエリー内でTransformerとして適用するか、独立型のTransformerツールを作成することができます。詳細については、Transformerのガイドを参照してください。

formatDataAsArrayなどのヘルパー関数を利用できますが、特別なフォーマット作業が必要な場合もあります。

Retoolでは、Firestoreの利用可能なコレクション(最上位または特定のドキュメントID下)を照会することもできます。これは、このテーブルに表示させたいドキュメントを動的に選択するのに使用できます。最初に、最上位のコレクションをリストする新規クエリーを作成します。

ネストされたコレクションを照会したい場合は、親ドキュメントIDを用意されたフォーム・フィールドに指定することができます(現在、コレクション・グループの照会をサポートできるように取り組んでいます)。それでは、このクエリーの結果をDropdownコンポーネントに取り込んで、このコンポーネントの値を元のlistDocumentsクエリーに接続しましょう。listDocumentsクエリーの設定が、手動でトリガーしたときではなく、入力が変更されたときに実行されるようになっているか確認してください(「General」タブの下部までスクロールします)。

ドロップダウンに新規クエリーlistCollections.dataプロパティが入力され、次にドロップダウンの値がlistDocumentsクエリーの「Collection ID」フィールドに渡されます(「Use raw id」を選択した後)。これで、新規コレクションをドロップダウンから選択するとテーブルが更新され、選択したコレクションのドキュメントが表示されます。ドロップダウンをクリックして右側のパネルを使用することで、カスタム表示値、プレースホルダー・テキスト、(表示される値の中の)初期値を設定することができます。

❗️

Firestoreのタイムスタンプ・フィールドの変更

Firestoreを照会すると、Retoolからタイムスタンプ・フィールドがISOフォーマットの文字列で返されます。

最近、Firebase Admin SDKのバージョンをアップグレードした後、数日間、この変更されたタイムスタンプ・フィールドがオブジェクトとして(_seconds_nanosecondsのキーとともに)送信されていました。永続的な動作を維持するために、この変更を取り消しました。

Firestoreのドキュメントの更新

Retool内でFirestoreのドキュメントを更新するには、ドキュメントのIDと、更新に使用するオブジェクトが必要です。新規クエリーを作成し(下部のパネルの+new)、Firebaseリソースを選択し、Service typeドロップダウンから「Firestore」を選択してAction typeで「Update Document」を選択します。

ドキュメントIDと値を指定する必要があります。「Value」フィールドについては、部分オブジェクトを渡すと、渡されたフィールドのみが更新されます。また、現在の値を渡した場合も機能します(現在と同じ値を持つフィールドなど)。

ここでは、UIを実装する方法がいくつかあります。

  1. Formコンポーネントに、更新したい各ドキュメント・フィールドのTextInputコンポーネントを含める
  2. JSONエディター・コンポーネントを使用して、値を直接更新し、変更した値をボタンで送信する
  3. Tableコンポーネントを編集可能にする

ユーザーのニーズに最適なパターンを選択できます。結果をJSONオブジェクトとしてフォーマットしてクエリーの「Value」フィールドに渡せば完了です。ドキュメントIDとValueのフィールドはどちらも動的であることを覚えておいてください。{{ }}で囲んだJavaScriptを使用すれば、ほぼ何でも渡すことができます。ここでは、Tableコンポーネントを使用してドキュメントの値を直接編集する方法を説明します。

前のセクションでは、テーブル上部のドロップダウンで指定されたコレクションからドキュメントを取り込むテーブルを作成しました。このテーブルを編集可能にしてバックエンドの更新クエリーに接続することで、個々のフィールド、または複数のフィールドを一括して更新することができます。最初に、テーブルをクリックし、右側のサイドバーの「Columns」セクションに移動して、各列(または複数の列)を編集可能になるよう更新します。

これでフロントエンドでの編集が可能になりますが、さらにupdateDocumentクエリーを接続して編集が実際に行われるようにする必要があります。まず、updateDocumentクエリーに移動してDocument IDとValueに正しい値を入力します。

Document IDについては、テーブルで現在選択されている行を{{ table.selectedRow.data._id }}で選択します。また、コレクションについては「Use raw id」をクリックして、ドロップダウンから選択された値を渡します(もちろんこれはハードコードすることもできます)。

テーブルで別の行をクリックすると、ドキュメントIDの変更予告が表示されます。これは、テーブルで複数行の選択を許可している場合は表示されません。JavaScriptで構文解析するかループを実行する必要があります(これについては後述します)。

Valueフィールドでは、行の更新時のみに現れるテーブルの特殊なプロパティ.recordUpdatesにアクセスします。.recordUpdatesプロパティはオブジェクトの配列で、編集された行につき1つ存在します。編集された行を含め、行の各フィールドには値が入っています。1行編集すると、.recordUpdatesの配列に1つのオブジェクトが含まれます。これを繰り返していきます。.recordUpdatesには、編集済みかどうかに関わらずテーブルの各フィールドに値が含まれることを覚えておいてください。これによって、updateDocumentクエリーに渡すのが実に簡単になります。なぜならFirestoreは変更されていないフィールドを無視するからです。

2行目の2つのセルを編集した場合は、table.recordUpdatesに1つの要素が含まれ、テーブルの2行目のすべての値が入ります(2つの編集済みのセルを含む)。この配列はインデックスを付けてそのままupdateDocumentクエリーのValueフィールドに渡すことができます。

最後に、このクエリーをテーブルに接続する必要があります。右側のテーブル設定で「table edit queries」セクションまでスクロールして、「bulk update action」ドロップダウンからupdateDocumentクエリーを選択します。これで、テーブルを編集できるようになりました。列のデータ型の更新(datepickerの使用などのため)、列名の変更など、あらゆる面でテーブルを使いやすくすることができます。

ただ1つ注意することがあります。テーブルの複数の行を一度に更新する場合、これは機能しません。複数の行を一度に更新するには、すべての行(つまり、レコード)で繰り返す別のクエリーを.recordUpdatesの中に作成して、各行にupdateDocumentクエリーを適用する必要があります。ここでは、その方法を説明します。

  1. 更新したレコードごとにupdateDocumentクエリーをトリガーするJavaScriptコードのクエリーを作成する

新規クエリーを作成し(下部のパネルの+new)、Resourceドロップダウンから「Run JS Code」を選択します。これでJavaScriptをクエリーとして実行させることができます。アプリの他の部分を参照するのに{{ }}を使用する必要はありません。Retoolの各クエリーには.trigger()メソッドが含まれています。ここでは、.recordUpdates配列を繰り返して要素ごとにupdateDocumentクエリーをトリガーします。ボイラープレートは以下のようになります。

const toUpdate = table.recordUpdates
toUpdate.forEach((record) => {
  updateDocument.trigger()
})

問題は、updateDocumentクエリーのDocument IDとValueの現在の値がここでは機能しないことです。Document IDは現在選択されているテーブル行から取り込み、Valueフィールドの.recordUpdatesではハードインデックス[0]を付けています。

  1. 追加のスコープをupdateDocumentトリガーに渡して、クエリーを実行するたびにDocument IDとValueを取得する

Run JS Codeクエリーを介してRetoolでクエリーをトリガーするときは、追加のスコープ(つまり、updateDocumentクエリーに対してこれから行うこと)を渡すことができます。Run JS Codeクエリーでは、レコードの_iddocument_idとして渡し、レコード・オブジェクト全体をdocument_objectとして渡すようにコードを更新します。次に、これらの値をupdateDocumentクエリーで参照します。

const toUpdate = table.recordUpdates
toUpdate.forEach((record) => {
  updateDocument.trigger({
    additionalScope: {
      document_id: record._id,
      document_object: record
    }
  })
})
  1. 追加のスコープを使用するようupdateDocumentクエリーを更新する

ここでは、document_iddocument_objectをRun JS Codeクエリーを介して渡します。これらの参照をupdateDocumentクエリー内で更新しましょう。これらの変数名はまだ実際には存在していないのでエラーがスローされます。これらはJS Codeクエリーを実行したときに定義されて、渡されます。

JS Codeクエリーは最終的にこのようになります。

  1. handleUpdatesクエリーを使用するようテーブルを更新する

これを機能させるためには、最後にテーブルに接続された一括更新クエリーをupdateDocumentからhandleUpdatesに変更する必要があります。

これをもう少し使いやすくする方法がいくつかあります。

  1. handleUpdatesクエリーが成功したときにlistDocumentsクエリーを実行すると、テーブルがリフレッシュされます。
  2. updateDocumentのクエリー成功の通知を無効にすると、画面がすっきりします。

問題が発生したら、いつでもconsole.logでブラウザーのコンソールにログアウトしてデバッグさせることができます。

🚧

Firestoreへのタイムスタンプ・フィールドの書き戻し

Firestoreのタイムスタンプ・フィールドに書き戻すには(また、文字列として設定しないようにするには)、データを日付型に変換する必要があります。Retoolはmoment.jsがプリインストールされた状態で出荷されるため、{ lastUpdatedAt: {{ moment() }} }などを書き込んでタイムスタンプを適切にフォーマットしておくことができます。長年にわたって信頼されているnew Date()も使用できます。

現在は、これらのタイムスタンプを更新クエリーまたは追加クエリーに直接書き込む必要があります。JS Codeクエリーを介してこれらを追加のスコープとして渡すと、文字列に変換されます。これは早急に修正する予定です。

Firestoreクエリーでの参照の使用

FirestoreクエリーでDatabase Refを利用する必要がある場合にRetool内で指定する特別な方法があります。これは、クエリーの値の中に$ref識別子で指定する方法です。

この構文はMongoDB JSON拡張構文から発想を得たものです。$refを使用してこれを指定すると文字列が参照になります。

ドキュメントの挿入

クエリー・エディターでResourceドロップダウンからFirebaseリソースを選択します。次に、Service typeで「Firestore」を選択し、Action typeで「Insert document」を選択します。Retoolでドキュメントを挿入するにはドキュメントの更新時と同じフォーマットが必要ですが、Firestoreに自動作成させる場合はドキュメントIDを空白にしておいてもかまいません。

ドキュメントの削除

クエリー・エディターでResourceドロップダウンからFirebaseリソースを選択します。次に、Service typeで「Firestore」を選択し、Action typeで「Delete document」を選択します。必要なパラメーターはドキュメントIDのみです。ここでは、テーブル内で現在選択されているドキュメントの_idプロパティを参照することで、これを動的に設定します。

IDによるドキュメントの取得

クエリー・エディターでResourceドロップダウンからFirebaseリソースを選択します。次に、Service typeで「Firestore」を選択し、Action typeで「Get Document by ID」を選択します。必要なパラメーターはドキュメントIDのみです。ここでは、テーブル内で現在選択されているドキュメントの_idプロパティを参照することで、これを動的に設定します。

Firebase Authの使用

Retoolでは、Firebase Authentication設定からユーザーの表示、更新、追加、削除を行うことができます。

📘

Firebase Auth Admin Panelテンプレート

Firebase Authデータ上での基本的なCRUDを検討している皆さまのために、取り掛かりやすいテンプレートを作成しました。詳細についてはこちらを参照してください。あるいは、Retoolのホームページで「create new」をクリックして「create from template」をクリックしてください。Firebase Auth Admin Panelテンプレートを探して作成しましょう。

ユーザーのリスト表示

既存のFirebase Authユーザーをリスト表示するには、新規クエリーを作成し(下部のパネルの+new)、ResourceドロップダウンからFirebaseリソースを選択し、Service typeドロップダウンから「Auth (user management)」を選択してAction typeで「List users」を選択します。結果は、{{ listUsers.data.users }}を介してTableコンポーネントに表示されます。

listUsersクエリーでは、クエリーから返されるユーザー数に制限を設定することができます。制限値(およびRetool内のほぼすべて)も動的に設定できるため、アプリ内でクエリーに制限を設定するテキスト入力に関連付けることができます。

テーブルにユーザーを取り込んだら、小さなフィルターのアイコンでフロントエンド・フィルターを適用することができます。

UID、電子メール、または電話番号によるユーザーの取得

UID、電子メール、または電話番号でユーザーを取得するには、新規クエリーを作成し(下部のパネルの+new)、ResourceドロップダウンからFirebaseリソースを選択し、Service typeドロップダウンから「Auth (user management)」を選択してAction typeで「Get user by email/UID/phone」を選択します。例えば、ユーザーの電子メールを使用すると、以下のようになります。TextInputコンポーネントが上部に追加され、その値がクエリーのUser Emailフィールドに渡されます。

ユーザーの削除

ユーザーを削除するには、新規クエリーを作成し(下部のパネルの+new)、ResourceドロップダウンからFirebaseリソースを選択し、Service typeドロップダウンから「Auth (user management)」を選択してAction typeで「Delete a user」を選択します。削除したいユーザーのUser UIDを渡す必要があります。ここでは、現在選択されているテーブル行から取り込みます(このテーブルにはlistUsersクエリーの結果が取り込まれています。)

ユーザー情報の更新

ユーザー情報を更新するには、新規クエリーを作成し(下部のパネルの+new)、ResourceドロップダウンからFirebaseリソースを選択し、Service typeドロップダウンから「Auth (user management)」を選択してAction typeで「Update a user」を選択します。ユーザーのUIDと更新オブジェクトが必要になります。

ユーザー情報を更新するUIは、フォーム、生JSONオブジェクト、さらには編集可能テーブルなど、さまざまな方法で設計することができます。テーブルを編集可能にしたい場合は、前述の手順に従い、Firestoreの場合と同じ作業を行います。

RetoolでのFirebase Authの一般的なユース・ケースは電子メール・アドレスの確認です。作成したこのupdateUserクエリーをこれに使用することができます。まず、ボタンをテーブルの下にドラッグして、そのラベルを「Verify Email」に変更します。次に、updateUserクエリーを実行するようにそのボタンを関連付けます。最後に、クエリーのUser UIDフィールドを現在選択されているテーブル行に設定し、ユーザー・オブジェクトとして{ "``emailVerified``"``: true }を渡します。

成功するとlistUsersをトリガーするようupdateUserクエリーが設定されているので、Verify EmailボタンをクリックするとユーザーのemailVerifiedフィールドが更新され、テーブルが再表示されます。

新規ユーザーの作成

新規ユーザーを作成するには、新規クエリーを作成し(下部のパネルの+new)、ResourceドロップダウンからFirebaseリソースを選択し、Service typeドロップダウンから「Auth (user management)」を選択してAction typeで「Create a user」を選択します。ユーザー・オブジェクトが必要になります。技術的に、Firebase APIではこのオブジェクトへのフィールドの組み込みは不要ですが、通常は少なくとも電子メールとパスワードを渡すものとします。

User objectフィールドは動的であるため、UI作成時に他のRetoolコンポーネント(フォーム、2つのテキスト入力など)から値を渡すことができます。