Monacaのサンプルアプリである「フォトシェアアプリデザインテンプレート」にバックエンド機能を実装する連載の第6回目。今回はフォロー/フォロワーの仕組みを作ります。

その1:認証編
その2:プロフィール編集
その3:写真アップロード
その4:写真検索
その5:いいね/コメント

データの管理法について

一般的なRDBMSで考えた場合、次のようなテーブル構成になるかと思います。

  • User : ユーザ情報が入ったテーブル
  • Follow : フォロー/フォロワー情報が入ったテーブル

FollowテーブルにはフォローしたユーザIDとフォローされたユーザIDが入り、相互にUserテーブルを参照します。しかし、この場合の問題点としてユーザがフォローするごとにFollowテーブルにデータが追加されていき、フォロー情報から写真データを取得する処理が重たくなっていくという点が挙げられます。

RDBMSではないmBaaSでは上記のような方法ではなく、ユーザ情報にfollowsというカラムを追加し、そこにフォローしたユーザのobjectIdを配列形式で追加していく方法で対応したいと思います。この場合、フォロー数が増えてもデータのレコード数は増えないので重たくなりません。
mBaaSではリレーションという仕組みも用意されているのですが、直接連結しているクラス(たとえば、フォロー情報→ユーザ情報)しか取得できません。今回、本当に必要なのは写真クラスのデータになりますので、フォロー情報とユーザ情報をリレーションで結ぶのはよくありません。

フォローしているユーザの数は次のようにして取得できます。

user.follows ? user.follows.length : 0

フォロワーは次のように取得します。Userクラス内で、followsカラムの中に自分自身のobjectIdが入っているデータの数を数えるという方法です。

ncmb.User
  .equalTo('follows', user.objectId)
  .count()
  .fetchAll()
  .then((result) => {
    // result.countがフォロワーの数
  })

画面遷移について

フォローは、ユーザ情報が表示されるページで行うことができます。このための処理を、写真を追加した時に実行される appendPhoto() 内に追加します。

const appendPhoto = (dom, photo) => {
  // 省略
  $(dom).find('.profile_image').on('click', (e) => {
    $('#nav')[0].pushPage('user.html', {animation: 'slide', data: {user: photo.user}});
  });
}

ユーザ情報ページに画面遷移した際に、すでにフォローしているユーザだった場合は Follow の文字を Unfollow にします。

if (current_user.follows && current_user.follows.indexOf(user.objectId) > -1) {
  $(dom).find('.follow').text('Unfollow');
}

また、自分のページだった場合はフォローボタンを無効にします。

if (user.objectId == current_user.objectId) {
  $(dom).find('.follow').attr('disabled', true);
}

フォロー処理について

フォロー処理は次のように実装します。すでにフォローしていればフォローを解除し、フォローしていなければフォローするという流れです。

$(dom).find('.follow').on('click', (e) => {
  let follows = current_user.follows;
  if (!follows) {
    // まだデータがない場合は初期化
    follows = [user.objectId];
  } else {
    // すでにフォローしているかチェック
    if (follows.indexOf(user.objectId) > -1) {
      // フォローしていればアンフォロー
      follows = follows.filter((u) => {
        return (u !== user.objectId);
      });
    } else {
      // フォローしていなければフォロー
      follows.push(user.objectId);
    }
  }
  current_user
    .set('follows', follows)
    .set('authData', {})  // ないとエラーになります
    .update()
    .then(() => {
      // フォロー状態をチェックしてボタンの文字を変更
      if (current_user.follows && current_user.follows.indexOf(user.objectId) > -1) {
        $(dom).find('.follow').text('Unfollow');
      }else{
        $(dom).find('.follow').text('Follow');
      }
    })
});

タイムライン表示について

タイムラインには、自分の写真とフォローしているユーザの写真を表示します。
実装方法は、フォローしているユーザのobjectIdを複数指定して、写真クラスに対してinで検索します。
タイムラインには写真のアップロード主であるユーザ名を表示しますので、includeを使ってユーザ情報も同時に取得するようにしています。

const loadTimeline = () => {
  const Photo = ncmb.DataStore('Photo');
  const follows = current_user.follows || [];
  follows.push(current_user.objectId)
  Photo
    .limit(10)
    .include('user')
    .in('userObjectId', follows)
    .order('createDate')
    .fetchAll()
    .then((photos) => {
      for (let i = 0; i < photos.length; i += 1) {
        const photo = photos[i];
        timelinePhotos[photo.objectId] = photo;
      }
    });
}

ここまでの処理でフォロー/アンフォロー処理ができあがりました。ソーシャル系サービスではよく求められる機能になりますので、実装方法を参考にしてもらえれば開発がスムーズになるかと思います。データを配列で持ち、行数を減らすのはmBaaSのスキーマレス型データベースではよく使われる方法になりますので、応用できる機会は多いはずです。

ここまでのソースコードはNCMBMania/photoshare at v0.6にアップロードしてあります。実装時の参考にしてください。