11.1 リレーションシップ(フォロー/フォロワー)テーブルの作成
第11章では、他のユーザーをフォロー(フォロー解除)する機能を構築していきます。
リレーションシップテーブルの作成
リレーションシップとは
データベースにおけるリレーションシップとは、データ間の関連づけのことを指します。複数のテーブル間で特定のフィールド(カラム)を関連づける機能のことをいいます。
これにより、テーブル間で同じ情報が重複するのを避けることができ、あるフィールドの入力を別のテーブルの特定のフィールドの項目から選択するようにしたり、複数のテーブルを連結して情報をまとめたりすることができます。
bubbleでのリレーションシップ
Tweetテーブルを見てみましょう。

Creatorは自動で作成されるカラムですが、データ形式が「User」になっています。Creatorには登録されたUserのデータを保持しており、TweetのCreatorカラムからUserのデータを呼び出すことができます。
実際に第10章でツイートの作成者のユーザー画像を表示しました。
このようにデータ形式を別のテーブルにすることにより、bubbleではリレーションシップを作成することができます。
また、bubbleではデータ形式(Filed type)をリストにすることができます。
例えばUserテーブルに以下のようにフォローしているユーザーをリストで保存するカラムを作ったとします。

このようにすると、Userテーブルのfollowカラムには複数のユーザーデータを保持することができるため、フォローしているユーザー情報を1つのカラムに保存できます。
ただし、リストには最大10,000個のアイテムまで、という制限があります。そのため、大規模なシステムには向きません。
一般的なデータベースでのリレーションシップ
一般的なデータベースでは、1つのカラムに複数のデータを持たせるのではなく、別のテーブルを作成します。複数のテーブルを関連付けるためのカラムを外部キーと呼びます。
例えば、フォローテーブルを作成した場合、「どのユーザー」が「誰をフォローしているか」のデータが必要です。
User | follow user |
ユーザーA | ユーザーX |
ユーザーA | ユーザーY |
ユーザーB | ユーザーX |
このようなデータがあった場合、Userカラムが外部キーとなります。
一般的なリレーションシップでは外部キーの各項目が複数のレコードに繰り返し登場することがあり、このような関係を「1対多リレーションシップ」と呼びます。外部キーの各項目が1つのレコードにしか現れないように制約する「1対1リレーションシップ」もありますが、使われる場面は少ないです。また、両テーブルのフィールドが互いに相手方へのリレーションシップを張った状態を「多対多リレーションシップ」といいます。
フォローテーブルを作成する
それでは、フォロー・フォロワーテーブルを作成しましょう。
フォローテーブルは、対象ユーザー、フォローしたユーザーを保持します。
Data > Data typesタブで、FollowというNew Typeを作成します。このとき、「Make this data type private by default」にチェックを入れます。これは Data > Privacy タブで細かく設定ができますが、テーブルのデータを閲覧できる条件を設定するものです。何も設定しないとアプリを利用していないユーザーも内容を確認できることになります。社内で利用するデータなどインターネット上に公開したくないデータは必ず設定するようにします。

Privacyタブに移動して見てみましょう。
デフォルトでは、Visible to creatorという名前で、フォローのレコードを作成したユーザーのみレコードを閲覧できる設定になっています。

実際には他ユーザーのフォローユーザーを確認できるようにしたいので、Current User is logged in に変更します。ログインしているユーザーのみFollowテーブルのデータを確認できるという設定です。

フォローユーザーを登録するカラムを追加します。

外部キーになるフォローしたユーザーのカラムは、Creatorをそのまま利用します。
フォローユーザーの取得方法
フォローテーブルから自分がフォローしているユーザーを検索するには、Creator = Current User(自分自身)で検索した結果のFollow User となります。
フォロワーの取得方法
ユーザーAがフォローされているユーザーを取得するには、Followテーブルから取得できます。ユーザーAがユーザーBをフォローしていれば、ユーザーBのフォロワーはユーザーAとなるためです。
従って、フォロワーを検索するときは Follow User = ユーザーB という条件にすることによって、Creatorがフォロワーになります。
具体的には第11章2で設定していきます。
11.2 フォローユーザーの一覧タブ/フォロワーユーザーの一覧タブにフォローしている&されているユーザーを表示してみる
マイページの設定
フォローユーザーの表示
ビジュアルエディタでmypageを表示し、Follow Userグループを表示します。
グループFollow UserのデータはCurrent Userを設定しておきます。

Repeating GroupのRG FollowUserのData sourceは、Followテーブルから自分がフォローしているユーザーを検索して設定します。
まず、Do a search for … でFollowテーブルを検索します。

これだけだと検索結果がFollowになってしまうため、続けて以下のように設定します。

こうすることで、「Followテーブルの自分自身が作成した(=自分がフォローしている)データのFollow User」を取得することになります。
RG FollowUserにフォローユーザーのデータが設定できたところで、内部のGroup FollowUserにはCurrent cell’s Userを設定します。

FollowUser Imageは、homeページのユーザー画像と同様に設定します。

Conditionalも設定しておきます。

ユーザー名、投稿数についてもParent group’s Userから取得します。


プロフィールのリンク先を設定します。

フォロー中の人数を取得するには、Followテーブルを検索します。

同様に、フォロワーの人数を取得するには、FollowテーブルのFollow User = そのユーザー という条件で検索します。

Custom Statesでタブの切り替え
次にフォロワーの表示を作成します。
フォロワー表示用のRepeating Groupは作成していませんが、タブ切り替えがされたときにRG FollowUserのData sourceを書き換えることで、一覧の内容をフォローユーザーとフォロワーで切り替えます。
もちろん、タブごとにRepeating Groupを作っても構いません。ただし、画面の要素が増えるほど画面表示に時間がかかるようになるので、そのまま利用できるところはそのまま利用するほうが最終的には良いです。
Group Tabに現在のタブ番号を保持するためのCustom Statesを作成し、デフォルト値を1とします。1:タイムライン、2:フォローユーザー、3:フォロワーとします。

tab_number = 3のときRG FollowUserのデータをフォロワーに変更するために、RG FollowUserのConditionalに設定します。

これで、タブ番号が3のときフォロワーが表示されます。
フォローユーザー・フォロワー一覧のページング
RG FollowUserのページングを設定します。第10章1と同様に設定していきます。
RG FollowUserのList on Numbersを配置し、プロパティを設定します。

RG User PagingにCustom Stateを作成し、Data souceを設定します。

ページングの番号のConditional

前のページ、後ろのページアイコンのクリック禁止


クリック時のワークフローは、homeページで作成したワークフロー4つをコピー&ペーストします。このとき、それぞれのワークフローでエレメントが見つからないなどのエラーが発生しています。1つずつワークフローのアクションを確認し、mypageに合わせた内容に修正しましょう。
例)Icon Prevのワークフロー
homeページのIcon Prev要素を起点としているため、mypageでは要素が見つかりません。Elementは Icon User Prevを選択します。

タイムラインの設定
タブ番号が1のとき、homeページと同様に投稿の一覧を表示します。mypageに表示する投稿は自分の投稿のみとします。
ビジュアルエディタでグループTimeLineを表示させ、Repeating Group Tweetに自分自身の投稿を設定します。

Creator Imageに画像を設定します。画像がないときの Conditonalも設定します。


ユーザー名の表示を設定します。

タイムラインのページング
RG User Pagingと同様に、ページング要素、ワークフローを設定します。
List of Numbersの追加と設定

RG PagingのCustom StatesとData source

Icon Prev、Icon NextのConditional


Text NumberのConditional

ワークフローも同様に設定してください。
タブ切り替えのワークフローを実装する
各タブの要素(TimeLine、FollowUser)ができたので、実際にタブをクリックした時に要素の表示を変更するワークフローを設定します。
タブをクリックした時に、Group Tabに設定したCustom Statesの値を変更します。
まず、Text Timeline をクリックしてワークフローを開始し、アクションはElement Actions > Set stateを選択します。
Text Timelineをクリックしたときは tab_number を1に設定します。

同様に、Text Followedをクリックしたときは tab_number を2に、Text Followerをクリックしたときは tab_number を3にするように設定します。


タブ切り替え時の表示/非表示を設定する
ワークフローでtab_numberの値を変更したので、tab_numberに合わせて要素の表示/非表示を設定します。
TimeLineをデフォルトで非表示にします。

Conditionalタブで、「tab_numberが1のとき表示」とします。

次にFollow Userですが、こちらはフォロー、フォロワーどちらも表示するため、Conditionalの設定は「tab_numberが1でないときに表示」とします。

これで、タブをクリックしたときにtab_numberが変更され、タブの内容が切り替わることになります。
最後に、現在選択しているタブがわかるよう、タブのConditionalを設定します。それぞれのタブ(Text Timeline、Text Followed、Text Follower)は、tab_numberの値に応じて背景色・文字色を変更します。



usersページの設定
続いて、usersページもmypageと同様にページの内容を設定しましょう。
URLパラメータから対象ユーザーを表示する
userページのグループBodyの内部、Group Userについて表示内容を設定します。このページでは、別ページからリンクを生成していましたが、
https://(アプリ名).bubbleapps.io/users?user=(ユーザーID)
としてアクセスされた場合、ユーザーIDの情報を表示します。
URLパラメータから情報を取得するには、BodyのData sourceに設定してあるよう、Get data from page URLというプロパティを利用します。

Typeを変更することで、テキストや数字、他のテーブルの値を受け取ることができます。
Group Userには Parent group’s User を設定します。これで、Bodyで取得しているユーザー情報を引き継ぎます。

ユーザー名、ユーザー画像、投稿数、プロフィールへのリンク、フォロー人数、フォロワー数についてはmypageと同様に設定しましょう。
タイムラインの表示
usersページのタイムラインは、表示しているユーザーの投稿のみとします。
まず、グループTimeLineにグループBodyのユーザー情報を設定します。

RepeatingGroup Tweetの検索条件は以下の通りになります。

現在表示しているユーザーが作成したTweetのデータのみを表示することになります。
※以下ではmypageと似た画面になるのでmypageをクローンすることで作業効率を上げることもできます。
フォローユーザーの表示
mypageと同様に設定してください。ここでは説明を割愛します。
フォロワーの表示
mypageと同様に設定してください。ここでは説明を割愛します。
タブの切り替え
タブクリック時のワークフロー、Conditionalの設定もmypageと同様に設定してください。ここでは説明を割愛します。
ユーザーのフォロー/フォロワータブにリンクする
mypage、userページともに、ユーザー一覧の「xフォロー中」「xフォロワー」のリンク先をクリックするとそのユーザーのフォロータブ/フォロワータブを表示するようにします。
パラメータでタブ番号を受け取る
フォロー中/フォロワーのリンク先のパラメータにタブ番号を追加します。
以下はusersページ > Body > Group User > Group Info > Link Followの設定です。

ユーザー一覧の「プロフィールを見る」リンクとほぼ同じですが、パラメータに tab が追加されています。
フォロワーのリンクでは tab = 3 と設定します。
この設定で、リンク先が以下のようになります。
https://(アプリ名).bubbleapps.io/users?user=(ユーザーID)&tab=2
同様のリンクの設定を、home、usersの全てのリンクに設定しておきましょう。
ワークフローでタブ番号を取得する
それでは、リンク先のusersページでページ読み込み時にタブ番号も取得できるようにしましょう。
具体的には、ページ読み込み時にURLパラメータにtabが存在した場合、Custom Statesのtab_numberを変更します。こうすると、自動的にタブを選択したことと同じになります。
Workflowタブでトリガーに「Page is loaded」を選択します。

アクションは Set state を選択し、Group Tabのtab_numberを URLパラメータから取得し設定します。
このとき、タブ番号を持っていないこともあり得るため、条件に「URLパラメータにtabがあるとき」としておきます。

これで、
https://(アプリ名).bubbleapps.io/users?user=(ユーザーID)&tab=2
というリンクの遷移先では、タブ2(フォロー)が選択された状態で表示されます。
自分自身のフォロー/フォロワーのリンク
homeやmypageにある自分自身のフォロー/フォロワーへのリンクは、mypageにリンクさせます。

マイページでは自分の情報を表示するため、userパラメータは不要です。
同様にフォロワーのリンクも作成します。

mypageにもタブを取得するワークフローを設定する
usersページと同様に、URLにtab番号があった場合、ページ読み込み時にtab_numberを設定する処理を追加します。

11.3 フォロー機能を実装
それではフォロー機能を実装して、画面上から確認してみましょう。
フォローボタンからワークフローを開始
usersの「フォローする」ボタンからワークフローを開始します。
フォローしたときにFollowテーブルにデータを追加するので、アクションはCreate a new thing…になります。

Follow Userには「フォローする」ボタンの親エレメントのユーザー(=表示しているユーザー)を登録します。
これでフォロー機能が実装できました。
同様に、usersページ > Body > Follow User > RG FollowUser > Group FollowUser > Button AddFollowにも同じアクションのワークフローを追加します。

フォロー中のユーザーのフォローボタンを非表示にする
今のままだとフォロー中ユーザーにもフォローボタンが表示されてしまうので、Conditionalタブで非表示にします。
自分がフォローしているFollow Userに対象のユーザーが存在するかどうか確認するには、「検索結果:countが0件より大きいとき」とします。

検索結果が0件より大きい場合は、非表示にしましょう。
フォロー一覧のフォローボタンを非表示にする
同様に、usersページでフォロー一覧を表示したとき、自分のフォローユーザーが一覧に表示された場合、フォローボタンを表示する必要がありませんので、この場合も非表示の設定を追加します。


mypageのフォローユーザー一覧のフォローボタンにも、フォロー機能の実装とフォロー中ユーザーのフォローボタンの非表示を設定しておきましょう。
自分自身のフォローをできないようにする
usersページで誰かのフォローユーザー/フォロワーを確認中に、自分自身が表示されたときフォローできないようにボタンを非表示にします。
フォローボタンのConditionalにもう一つ、「このユーザーが自分自身だった場合非表示」という条件を追加します。

他のフォローボタンにも同様の設定を追加します。
11.4 フォロー解除機能の実装
フォローボタンからワークフローを開始
usersページの「フォロー解除」ボタンからワークフローを開始します。
フォローしたときにFollowテーブルからデータを削除するので、アクションはDelete thing…になります。

削除するデータは、Followテーブルに存在する、Follow Userが該当ユーザのものです。ただし、Do a Search forの検索結果は1件であってもリストとして認識されます。特定のユーザーをフォローしているレコードは1件しか存在しないため、続けて「:first item」を追加します。これで、検索結果の1番目のレコードを指定していることになります。

これでフォロー解除機能が完成です。
その他の「フォロー解除」ボタンにも同様のワークフローを設定してください。
フォロー中のユーザーにのみフォロー解除ボタンを表示する
フォロー解除ボタンはデフォルトで非表示となっているので、フォローボタンを表示した条件と同様に、Followテーブルにデータがあればフォロー解除ボタンを表示するように設定します。

その他のフォロー解除ボタンにも、同様のConditionalを設定してください。
11.5 ユーザー検索を実装する
ユーザー一覧を作成する
usersページに遷移した時、userパラメータが存在しないときはSearch Bodyを表示し、ユーザーを検索できるようにページレイアウトを作成していました。
まず、ユーザー一覧を表示させ、次に検索機能を追加します。
ユーザー一覧を表示する
ビジュアルエディタでusersページを表示し、Search Bodyを表示させます。Conditionalには以下のように設定されています。

RG Userlistには、全ての登録済みユーザーが表示されるようになっています。

その他、以下の要素について他のページと同様に設定します。
・RG Userlist内部のユーザー名、ユーザー画像、ツイート数、リンクなど
・フォローする/フォロー解除ボタンのワークフロー、Conditional
・RG Search Pagingのワークフロー、Conditional
検索機能を実装する
検索するボタンをクリックした際に、入力した内容で検索を実行するワークフローを作成していきます。
まず、検索するボタンからワークフローを開始します。
Repeating GroupのData sourceを書き換えるので、Element actions > Display listアクションを選択してください。

ElementはData sourceを書き換えるエレメント、RG Userlistを選択します。
Data sourceには、「UserテーブルのnameカラムをInput Searchの値で検索する」と設定します。このとき、部分一致にするには「=」でなく「contains」を利用します。

「Ignore empty constraints」にチェックを入れておきます。これは、Input Search’s valueが空の場合、空の値を無視するということになります。チェックが入っていないと、Input Search’s valueが空のとき、nameが空を含むUserを検索するということになり、検索結果が0件になります。
ヘッダーのリンク先を設定する
プロパティエディタでReusable elementaのヘッダーを表示し、リンク先をそれぞれ設定してください。



フォローするユーザーをDataタブから登録しておく
今のままだとユーザーデータが1件しかないかと思いますので、Dataタブからユーザーデータを追加しておきます。

All Usersを選択した状態でNew entryをクリックします。

ポップアップが表示されるので、Name、Emailを入力しCREATEをクリックしてください。このユーザーでログインをしなければ、存在しないメールアドレスでも問題ありません。
プレビューで検索〜フォローを確認する
それでは、mypageをプレビューします。

ヘッダーの「ユーザー一覧」をクリックします。

自分自身には「フォローする」ボタンが表示されていないことがわかります。
「テスト」で検索してみます。

部分一致するユーザーが表示されました。
フォローするをクリックして、フォローしましょう。

フォロー解除ボタンが表示されました。
ここで、マイページに戻りフォロータブをクリックするとフォローユーザーに「テスト」ユーザーが追加されていることが確認できます。また、フォロー中の人数も変更されています。

フォローしているユーザーのプロフィールを見てみましょう。URLにuserパラメータが含まれていることが確認できます。

また、フォロワータブに移動すると、自分がリストに表示されることが確認できます。

このページのまま、ユーザーのフォローを解除してみましょう。

フォローするボタンが表示され、フォロワーに自分が表示されなくなりました。
「テスト」ユーザーのフォロー中リンク、フォロワーリンクをクリックしてみましょう。URLにtabがあることが確認でき、意図した通りのタブが表示されるか確認してください。

11.6 タイムラインページの投稿表示をフォローしているユーザーの投稿に限定して表示させる
タイムラインの表示を修正する
homeのTimeLineの表示を修正する
投稿一覧をフォローしているユーザーの投稿に限定するには、Repeating Groupの検索条件を変更します。
homeページのTimeLine > Repeating Group Tweetのプロパティエディタを表示します。

ここで、フォローしているユーザーの投稿のみにするには、「TweetのCreatorが自分のフォローしているユーザー」になるように条件を変更します。
Creatorは、フォローしている複数のユーザーを指定することになり、こういった場合は is in を利用します。

is in (複数のデータ) とすることで、複数のデータのどれかを含むものを検索してくれます。具体的にはユーザーA、ユーザーBをフォローしている場合、Created By is in [ユーザーA, ユーザーB]となり、ユーザーAまたはユーザーBが作成した投稿を検索する形になります。
複数のデータ部分には、自分がフォローしているユーザーの検索結果を設定します。

自分がフォローしているユーザーはSearch for Follows:each item’s Follow Userで取得できます。Search for Followsの検索条件は以下の通り、Created By = 自分 とします。

これで、タイムラインの投稿は自分がフォローしているユーザーの投稿に限定されました。
ただし、このままだと自分がフォローしているユーザーの投稿のみになってしまい、自分の投稿が表示されません。自分の投稿をタイムラインに追加させるには、Created By is in [ユーザーA, ユーザーB, 自分] とする必要があります。
このようにリストにアイテムを追加する場合、リスト:plus item アイテム という式が用意されています。
:each item’s Follow Userをクリックし、Moreを表示させます。


Moreをクリックし、:plus itemを選択します。

続けてCurrent Userを選択し、以下のようにします。

これで、is in の後が [ フォローしているユーザーのリスト, 自分 ] となります。
11.7 最後に
第11章ではコアなソーシャル機能であるフォローの方法について実装することを通し、テーブルのリレーションシップについて学びました。
テーブルのリレーションシップはさまざまな要件で使用されます。
例えば、ECサイトではユーザーの商品を複数カートに追加しますが、この場合も同様の考え方でカートテーブルを作成します。
また、社内システムではユーザーは会社や部署に所属するため、部署テーブルを作成し、ユーザーと部署テーブルがリレーションシップの関係になります。
エクセルで同様のデータを作成する場合は、同じ部署名の場合部署名をコピーすると思いますが、テキストのデータを保存しておくとスペースが入っていたり、大文字と小文字を間違えたりした場合に同じデータとして扱えなくなります。そういった問題が起きないためにも、リレーションシップを利用して同じデータを複数回入力しなくても済むようにします。
慣れるまでは考え方が難しいかもしれませんが、同じデータが複数回出現する場合にはリレーションシップテーブルで解決できないか考えてみましょう。
第11章までで、基本的な簡易版Twitterの機能が完成しました。
お疲れ様でした。