2019-10-24に更新

Laravel+Vue.jsでユーザーのフォロー機能を作る

LaravelとVue.jsを使い、TwitterやQiitaのように画面を切り替えずにボタンクリックでユーザーのフォローとフォロー解除をできるようにしてみます。

今回は下記の構成で作ってみます。

  • Laravel5.8
  • Vue.js2.5
  • Bootstrap4.2

LaravelとVue.jsはもうちょっとバージョンが低くても大丈夫だと思います。Bootstrapについては、今回通信中のローディング表示にspinnerを使っているため最新のものが望ましいです。(4.0だと動きませんが、そこの部分のCSSをコピペしてくれば一応動きます)

テーブルの作成

ユーザーはLaravelのデフォルトのもので大丈夫です。ただし今回の例はモデルをapp/Modelsに移動していますので、同様の構成にしておく必要があります。ただ、他の設定ファイルの修正などもあり面倒ではありますので、逆に当記事に書いているuse App\Models\User等をuse App\User等に読み替えて試していただいたほうが楽かもしれません。

あとはフォローするユーザーとフォローされるユーザーを繋げるための中間テーブルを作ります。

マイグレーションは下記のような感じです。普通の命名規則としては双方のテーブルの単数名を組み合わせるのですが、そうするとuser_userになってしまい変な感じとわかりにくい感じがしたので適当に変えています。

Schema::create('follow_users', function (Blueprint $table) {
    $table->unsignedInteger('user_id');
    $table->unsignedInteger('followed_user_id')->index();

    $table->unique(['user_id', 'followed_user_id']);

    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->foreign('followed_user_id')->references('id')->on('users')->onDelete('cascade');
});

モデルの準備

中間テーブル作成

直接テーブルを操作したいので一応モデルも作っておきます。中間テーブル用のPivotを継承し、今回のテーブル構成に合わせて設定もいくつか行っています。

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class FollowUser extends Pivot
{
    protected $table = 'follow_users';
    public $timestamps = false;
    protected $guarded = [];
}

リレーションの設定

ユーザー(User) 中間テーブル(FollowUser) フォローされるユーザー(User)

という構成のリレーションを設定します。具体的にはLaravelのマニュアルでいうところのMany To Manyで、belongsToManyで設定します。

ユーザーのモデルに下記のメソッドを追加します。

    public function followUsers()
    {
        return $this->belongsToMany(self::class, 'follow_users', 'user_id', 'followed_user_id')
            ->using(FollowUser::class);
    }

普通はこんなに設定は不要でマニュアル通りで良いのですが、今回は同じテーブル同士のMany To Manyのためちょっと設定が増えています。

フォロー、フォロー解除処理作成

FollowUserControllerに下記のフォローとフォロー解除用のメソッドを追加します。

    public function store(Request $request)
    {
        $followedUser = User::findOrFail($request->input('id'));
        FollowUser::firstOrCreate([
            'user_id' => Auth::id(),
            'followed_user_id' => $followedUser->id,
        ]);
        return response()->json(['result' => true]);
    }

    public function destroy($id)
    {
        $followedUser = User::findOrFail($id);
        $user = Auth::user();
        $user->followUsers()->detach($followedUser->id);
        return response()->json(['result' => true]);
    }

ファイルの戦闘のnamespaceのあとに上記で利用しているクラスのuseも追加しておきます。

use App\Models\FollowUser;
use App\Models\User;

上記のような感じで、attach, detachを使うことで割当と解除を行うことができます。上記だとattachを使っていませんが、複数のタブを開いていて変な操作をした場合などに重複エラーが出てしまい、わざわざそのための処理を入れるのも面倒だったためfirstOrCreate呼び出しで完結するようにしています。

各メソッドの最初のユーザー取得も実際には不要ですが、一応変なデータが出来ないようにユーザーの存在チェック代わりに入れています。

Vue.js側でボタン作成

サーバー側は出来たので、あとは実際にフォローをトグルするためのUIをVue.jsで作っていきます。

スクリプトの定義部分

ひとまずコンポーネントのスクリプトの定義周りは下記のような形にしました。(TypeScriptで書いているので、JavaScriptの場合は適宜置き換えてください)

import { Vue, Component, Prop } from 'vue-property-decorator'
import axios from 'axios'

@Component
export default class UserFollow extends Vue {
  @Prop({
    type: String,
    required: true
  })
  id!: string

  @Prop({
    type: Boolean,
    default: false
  })
  following!: boolean

  currentFollowing = this.following
  sending = false

最初のフォロー状態はプロパティで受け取りますが、あとはボタン操作によって切り替えるため、コンポーネント内ではcurrentFollowingを使っていきます。ローディング表示や、ボタンの連打などを防ぐためsendingフラグを入れています。

フォロー、アンフォローの実行

フォローボタン、フォロー解除ボタンを押した時のメソッドを追加します。(エラー処理は省略しているため適宜入れてください)

  async follow() {
    if (this.sending) {
      return
    }
    this.sending = true
    const data = { id: this.id }
    await axios.post('/follow-users', data)
    this.currentFollowing = true
    this.sending = false
  }

  async unfollow() {
    if (this.sending) {
      return
    }
    this.sending = true
    await axios.delete(`/follow-users/${this.id}`)
    this.currentFollowing = false
    this.sending = false
  }

ボタンの配置

あとは上記を呼び出すためのボタンを配置します。

<template>
  <div>
    <button v-if="currentFollowing" type="button" class="btn btn-point btn-raised" @click="unfollow">
      <div v-if="sending" class="spinner-border spinner-border-sm" role="status">
        <span class="sr-only">Sending...</span>
      </div>
      <div v-else>フォロー中</div>
    </button>
    <button v-else type="button" class="btn btn-default btn-raised" @click="follow">
      <div v-if="sending" class="spinner-border spinner-border-sm" role="status">
        <span class="sr-only">Sending...</span>
      </div>
      <div v-else>
        フォローする
        <i class="material-icons">add</i>
      </div>
    </button>
  </div>
</template>

sendingがtrueの場合はspinnerのくるくるローディングが表示されます。また、フォロー状態によってボタンも切り替わります。

まとめ

以上のような感じでスムーズに使えるフォロー、フォロー解除ボタンを作ってみました。参考にできる部分があれば是非利用してみてください。その他何かこんなものの作り方を教えて欲しい! みたいなあればお気軽にコメントいただければと思います。

ツイッターでシェア
みんなに共有、忘れないようにメモ

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント