LaravelとVue.jsでいいね機能実装してみた

2021.01.18

見出し画像

今回は投稿に対するいいね機能を実装します。

シンプルに非同期でのいいね(解除)をしているので、特に難しいことはしていません。axiosで実装します。

目次

  1. 大まかな流れ
  2. 前提
  3. マイグレーションファイル、モデルなど作成、設定
  4. コントローラー、ルーティング
  5. コンポーネント設定
  6. itemモデルにコード追加
  7. コントローラーアクション設定
  8. おまけ
  9. まとめ

大まかな流れ

マイグレーションファイル、モデルなど作成、設定

コントローラー、ルーティング

コンポーネント設定

コントローラーアクション設定

上記のような流れで実装します。

前提

投稿機能は実装されている、という形で進めていきます。

マイグレーションファイル、モデルなど作成、設定

いつも通り、-mをつけてマイグレーションファイルも一緒に作成します。

php artisan make:model Like -m

onDelete()も使って投稿が消された場合は紐づくイイねも消えるようにします。

public function up()
   {
       Schema::create('likes', function (Blueprint $table) {
           $table->id();
           $table->foreignId('user_id')->constrained('users')->onDelete('cascade');
           $table->foreignId('item_id')->constrained('items')->onDelete('cascade');
           $table->timestamps();
       });
   }
class Like extends Model
{
   protected $fillable = ['user_id', 'item_id'];

   public function user()
   {
       return $this->belongsTo('App\User');
   }

   public function item()
   {
       return $this->belongsTo('App\Item');
   }
}

itemとuserテーブルはhas_manyの記載を書くとアソシエーション完成です。こんな感じで書いたらコマンド実行します。

php artisan migrate
上手くいかない場合は
php artisan migrate:fresh

コントローラー、ルーティング

コントローラー作成。命名規則は単数/複数どちらも可能です。(だったはず)特にアクションはまだ書いていきません。

php artisan make:controller LikeController

次はルート。checkアクションはvue側で呼び出して使います。

Route::get('/items/{item}/check', 'LikeController@check')->name('like.check');
Route::resource('items.likes', 'LikeController', [
 'only' => ['store'],
]);

コンポーネント設定

blade側はitemのshow画面で進めます。投稿したユーザー以外の場合にlikeのコンポーネントが表示される形です。

@if($item->user_id != Auth::id())
   <like :item_id="{{$item->id}}"></like>
@endif

コンポーネント側

<template>
 <div>
1  <button v-if="status == false" type="button" @click.prevent="like" class="btn btn-outline-warning">Like</button>
   <button v-else type="button" @click.prevent="like" class="btn btn-warning">Liked</button>
 </div>
</template>

<script>
export default {
 props: ['item_id'],      
 data() {
   return {
     status: false,
   }
 },
 created() {
   this.like_check()      2
 },
 methods: {
   like_check() {
     const id = this.item_id
     const array = ["/items/",id,"/check"];
     const path = array.join('')
     axios.get(path).then(res => {
       if(res.data == 1) {
         this.status = true
       } else {
         this.status = false
       }
     }).catch(function(err) {
       console.log(err)
     })
   },
   like() {                         3
     const id = this.item_id
     const array = ["/items/",id,"/likes"];
     const path = array.join('')
     axios.post(path).then(res => {
       this.like_check()
     }).catch(function(err) {
       console.log(err)
     })
   }
 }
}
</script>

1, ここでは現在のユーザーがこの投稿に対して既にいいねをしているかどうかで表示するボタン、動くアクションを変更しています。

2, 1のif文で使用している値を取得、代入している部分です。axiosでコントローラーまで飛び、返り値によってstatusに何を代入するかを決めています。

3, likeが呼ばれるとaxiosでコントローラーに飛び処理されます。飛んで行く先はどちらも同じアクションです。また処理が終わると2を再度実行し、statusの値を更新します。作成なのか解除なのかはコントローラーで判断します。

itemモデルにコード追加

itemモデルにisLikedを設置します。これは既にいいねしているかどうかを判断するのに使用します。exists()メソッドを使用します。

public function isLiked($user_id)
{
  return $this->likes()->where('user_id', $user_id)->exists();
}

コントローラーアクション設定

まずはコードを。

public function store(Item $item)
   {
       $user = Auth::user();
       if($user->id != $item->user_id) {
1          if($item->isLiked(Auth::id())) {
               // 対象のレコードを取得して、削除する。
               $delete_record = $item->getLike($user->id);
               $delete_record->delete();
           } else {
2              $like = Like::firstOrCreate(
                   array(
                       'user_id' => Auth::user()->id,
                       'item_id' => $item->id
                   )
               );
           }
       }
   }

1, 先ほどモデルに設定したisLiked()を呼び出しログイン中のユーザーがこの投稿に対して既にいいねしているかの条件分岐です。

2, イイねしていない場合は作成します。arrayを使って実装してますが、もっと良い書き方がある場合はご教授ください。

おまけ

下記のようにすればイイねの数とかも表示出来ますね。

<p class="card-text mb-0"><small class="text-muted">いいね数<{{$item->likes->count()}}</small></p>

まとめ

コントローラーでisLiked()を呼び出すことで1つのアクションだけで作成、解除が実装出来る。

なのでvueのアクションも1つで足りる。

こんな感じですね!そこまで複雑ではない実装かと思います〜。

今回はこんな感じで!

以上!