はじめに
Bubbleのプラグインを作ろうとしていて、わからなくて数日試行錯誤してしまったので、結論を書いておきます。時間をかけて試行錯誤し、少しいじるとすぐ動かなくなり、またBubbleのサーバー処理はデバッグがすごく大変なので、自分用に記録を残しておきたいという思いが強いです。
参考にしたBubble Forumの記事
Output an object for server-side/client-side - Tips - Bubble Forum
こちらの投稿をもとに、私が試行錯誤した内容です。
課題
まず、Bubbleは、ノーコード or ローコードでアプリが作れるSaaSです。私は、そこで使用するためのプラグインを作ろうと思いました。
作ろうとしてみたプラグインは、Supabaseからデータを抽出するというもの。
SupabaseのAPI呼び出しは、難なくできました。Bubbleを使わず、Make.comで作ってみたんですが、サラっと取得できました。
難しかったのは、BubbleからAPI呼び出しをして、その結果をクライアント側に返すということ。それが今回の課題で、この記事です。
ちなみにAPI callsだけで済む要件なら、それがベストです。今回は、サーバーサイドのActionを使いたかった。
課題:Bubbleのプラグインで、Supabaseへアクセスしてデータを抽出し、BubbleのWorkflowで扱えるようにする
制約1:サーバーサイドのActionsの処理
プラグインからSupabaseへのアクセスは、APIキーなどを渡すため、クライアントサイドではなく、サーバーサイドで動かす必要があります。そうしないと、BubbleアプリのユーザーへAPIキーが漏れてしまう。
また、引数などを柔軟に扱える"Actions"で行いたいということも制約です。(API callsではないという意味)
制約2:任意のテーブル、任意の列
プラグインとして提供するからには、プラグイン利用者(=アプリ開発者)の持つ、様々なテーブルや列にアクセスできないといけません。「test_table」から「col1」と「col2」を取り出すことがハードコーディングされていては、使い勝手が悪いということです。当たり前です。
つまり
サーバーサイドの戻り値として、呼び出し側が指定した自由な形を返したい。クエリを投げたら、↓こんなレスポンスを得たい。(Google Analyticsの日ごとのユーザー数、セッション数です)
SELECT date, users, sessions FROM ga_data_daily_summary;
[
{
"date": "2024-11-22",
"users": 100,
"sessions": 200
},
{
"date": "2024-11-23",
"users": 110,
"sessions": 210
}
]
※ 後述しますが、最終的にはこの形ではない形式で、この内容を得ます。
サーバーアクションから、JSONを返す設定方法
ではいきなり解です。
1.APIを定義する
API callsの本来の機能は使わないのですが、ここで、戻り値の型を定義します。
ここで設定する API Name
と Name
は、後で出てきます。ResponseType (Supabase)
のような形で、画面のElementなどの型として使われます。
ここでは、APIのURL等は指定しなくて、いきなり一番下の Manually enter API response
をクリックして、「こんなオブジェクトが返るよ」という指定をします。(参考記事では、APIを呼ぶステップがありますが、省略可能です。やってもいいです。)
このオブジェクトを1つ返す。db_records
の中にcolumns
があって、columns
にクエリ結果の1行が指定された列の順番に入っていると。
実際はこういうイメージですが、この入力欄では繰り返しが省略されます。
{
"db_recrods": [
{
"columns": [
"2024-11-22",
"100",
"200"
]
},
{
"columns": [
"2024-11-23",
"110",
"210"
]
}
]
}
SAVEボタンを押すと、「あなたが入力したのはこの形ですね?」っていう書き直された画面が出てきます。ここに表示されている情報がとても重要なので、しつこく書きます。
① ResponseType
型は、こういう型です
② db_records
というキーを持ち、型はリスト(配列)です
③ このリスト(配列)を、ResponseType db_record
型と呼びます
④ ResponseType db_record
型のリストの1要素は、columns
というキー値を持ち、リスト(配列)です
⑤ columns
のリストの1要素は、全部text型です。
本当は、columns
には、[文字列、数値、数値]
のように、汚いですがいろいろ入れたい。JavaScriptならできそう。でも、全部そろってる必要があります。これは、httpサーバーがクライアントへ返すものだからしかたないのかもしれません。
注意
よく見ると、私はManually enter API responseで"db_records"と入力したのに、③では"db_record"と"s"を取られています。また、ResponseTypeとはスペース区切り。
もし私が、"ResponseType"でなく"Response Type"、"db_records"でなく"db records"と名付けたすると、"Response Type db record"という型になり、固有名詞なのか一般名詞なのかまったくわからなくなるので、非常に注意が必要です。returnとかvalueとかrecordとかそういう単語は、Bubble用語としてもいっぱい使われるので、自分用の"db_records"のような単語にした方が良いです。(それでも勝手に"s"を取ったりしますが)
2.Actionsを作る
2.1. 設定項目
Get RecordsというActionを作ります。
① 名前は、"Get Records"
② Action typeは、"Server side"。(APIキーを扱うため)
テーブル名、列名のFieldは割愛
次からがキモ。
③ "RR_TYPE"という名前でFiledを定義し、④Captionは"Return Record Type"、⑤Editorは、"App Type"
⑥ 戻り値の変数名は"RR"、⑦Captionは"Returned Records"、⑧Key Typeは、"as RR_TYPE"
③~⑤と、⑥~⑧の塊が重要です。
まずなかなか思いつかないけどField(入力引数)として、戻り値を定義します。⑤でApp Type
と指定します。
そうすると、次のReturn values(戻り値)として、Key Type(戻り値の型)をas RR_TYPE
と指定できます。
ステップ1でAPI callsを作りましたが、それを⑤で受けて、⑧で利用する感じです。
ちなみに③と⑥が急に全部大文字ですが、これは私の都合です。この名前は表に出ない項目名(開発用の変数)だと思っているのですが、万が一どこかに出てきたらすぐわかるように、ちょっと目立つ変数名にしてあります。
2.2. Action code(function)
Supabaseから抽出する方法は割愛します。ここでは、レコードを取得して、こんな形のオブジェクトになりますという、functionの戻り値の型だけ。
async function(properties, context) {
/* ~ Supabaseの処理は割愛 ~ */
return {
"RR": {
"_p_db_recrods": [
{
"_p_columns": [
"2024-11-22",
"100",
"200"
]
},
{
"_p_columns": [
"2024-11-23",
"110",
"210"
]
}
]
}
};
}
- 戻り値は、オブジェクトで、Return valuesで定義した"RR"というキー値に入れる
- "RR"の値は、API callsで定義した型をちょっと変える
- キー名にすべて、
_p_
をつける
- キー名にすべて、
この2点ですかね。
どちらもなかなかわかりづらいですが、特に2点目は、どうしてこれを導出できたのか(公式HPにはない)わかりません。とにかくここで、_p_
をつけて返すと、API callsで定義した型に違反したことにはならないまま、プラグイン利用者(APP開発者)がこの変数にアクセスできるようになるんです。
知っていればすぐかもしれませんが、なかなかの難所です。
ここまでがプラグインを作る側の話で、次はついでに、アプリ開発者が利用する方法です。
3.アプリ側の利用方法
3.1.Designの設定(UI)
DBからレコードを取り出し表示するので、Repeating GroupというElementを使います。
そのプロパティの1つ目の、"Type of content"で、ResponseType db_record (Supabase)
を選びます。困ったことに、ResponseType (Supabase)
というのもあるので注意です。切れて末尾まで見られませんが、一度選択すると、カーソルを入れられるので、カーソルを右に移動すると末尾まで見れたりコピーできたりします。(プロパティダイアログの幅は、たぶん変更不可。すごい仕様)
つまりこの繰り返しのハコは、API callsの設定時に確認画面で表示された型の③ResponseType db_recrod
です、ということを設定しました。ハコの1つ1つの中からは、columns
を取り出せる状態です。
実際、Repeating groupの中にテキストを置くと、下記のように設定できます。Current cell's ResponseType db_record (Supabase)'s columns:first item
。
3.2.Workflowの設定
私は、ボタンを押したらDBから取り出す、という処理にしましたが、トリガーはなんでもいいでしょう。
Workflowでは2段階です。1段階目は、プラグインを使ってデータを取り出す。2段階目は、それをRepeating groupへ設定する。
3.2.1.プラグインを使ってデータを取り出す
テーブル名とか列名とかを入力することにしてますが、それは置いといてここで大事なのは、"Return Record Type"です。このネーミングは、私が2.1の④でつけたものです。必要に応じて変えてください。
そしてその型はResponseType (Supabase)
です。さっきのRepeating groupで使ったのとは別の方です。これは、functionの戻り値としては、API callsで定義したResponse Type
の全体を受け取りますという意味です。(ここは1つにした方が絶対いい。うまいやり方がありそう🤔)
3.2.2.Repeating groupへ設定する
"rg2"というのは、Repeating groupの名前です。変な名前ですみません。
そこへ、Result of step1 (Get Records (test...)'s Returned Record's db_records
を設定しています。マルカッコ閉じが1つないのは、Workflowの項目名が途中からマルカッコ閉じも含めて省略されているからですが、わかりづらい。
とにかく長いですね。
①3.2.1の結果の、②Returned Recordsの、③db_recrods。順に説明します。
①は、Workflowの1つ目の項目名が Get Records (testing)
(プロパティのタイトルに出てる)でしたので、その結果という意味。
②は、Actionsの設定項目で設定した項目名でした。2.1の⑦の名前です。
③は、関数から、_p_db_records
と返したオブジェクトです。{columns:[]}
のリストです。それが3.1で作ったRepeating groupの型と一致するので、値を設定することができます。
別解:サーバーサイドからJSON.stringify
した文字列を返す
別解というか、これが真の正解なんじゃないかと思ったりします。そしてプラグイン利用者・開発者が、JSON.parse
する。
試行錯誤の闇の中にいるときに気づきましたが、意地になってなんとか今回の課題をクリアした感じです
おわりに
今回の技術を使いたい人はどれだけいるのかわかりませんが、一部の人には刺さるかもしれませんので、書いてみました。もとにした投稿が2019年の投稿記事なので、今回の件が、いまさら公式に対応されたり、逆に使えなくなったりすることはない気がします。
Bubbleは、ノーコードっぽい気の利かせ方をして、なかなかのエンジニア泣かせです。
- "records"と名付けた変数を"record"に変えてくれたり
- その名前、どこから持ってきてるの?ってなる
- Captionに設定した項目が、ユーザーの画面上に出てきてそれを選択する必要があったり
- 注釈だと思ってたらそれがユーザーの項目名で、変数名は完全に裏方に徹する名前だったり
- 一般的にはパラメーターと呼ぶものが、Fieldっていう呼び名だったり
今回はもはやノーコードでもローコードでもないんですが、これはプラグイン作成だからかな。いや利用者も、戻り値をstringからdateやnumberへ変換する必要がある。一般的に利用してもらうには、もう少し工夫必要そうです。
というかそもそも、私がSupabaseからデータを取り出したいAppを作るとしたら、プラグインなんて使わず、Appから直接SupabaseのAPIを呼び出すな。それすらできない人が、Supabaseからデータ取り出したくなるかな。ま、私の想像を超えるいろんな事情があるだろうから、もう少し作りこんで一応公開してみよう。
ではよきBubblyライフを~。