LaravelとVue.jsでコメント機能実装

2021.01.18

見出し画像

今回は投稿に対するコメント機能を実装していきます。crud全て実装しますが、難易度が上がるのはupdate処理かと(個人的に)

まずは、大まかな流れを。

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

コントローラー、ルート設定

コンポーネント設定(作成、削除)

コンポーネント設定(一覧表示)

コンポーネント設定(更新処理)

こんな感じです。vueはそれぞれ分けて見ていきます。

目次

  1. 前提
  2. マイグレーションファイル、モデル作成、設定
  3. コントローラー、ルート設定
  4. コンポーネント設定(作成)
  5. コンポーネント設定(一覧表示, 削除)
  6. コンポーネント設定(更新処理)
  7. まとめ

前提

投稿機能は実装されている形で進めていきます。あとコンポーネントファイルは適当な所に入れてください。

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

いつも通り、-mをつけて同時作成、マイグレーションファイルとモデルにアソシエーションを設定します。

php artisan make:model Comment -m
public function up()
{
   Schema::create('comments', function (Blueprint $table) {
       $table->id();
       $table->string('text');
       $table->foreignId('user_id')->constrained('users')->onDelete('cascade');
       $table->foreignId('item_id')->constrained('items')->onDelete('cascade');
       $table->timestamps();
   });
}

userとitemモデル側はhas_manyの設定が必要です。(ここでは書きませんが)

class Comment extends Model
{
   protected $fillable = ['text', 'user_id', 'item_id'];

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

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

コントローラー、ルート設定

コントローラー作成し、アクションも書きます。

php artisan make:controller CommentController

アクション設定。普通に新規、更新、削除を設定してます。

get_commentsはコメント一覧を取得し、そのコメントに紐づくユーザーも一緒にしています。

use Illuminate\Http\Request;
use App\Item;
use App\Comment;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\CommentCheck;

class CommentController extends Controller
{
   public function get_comments(Item $item) 
   {
       foreach($item->comments as $comment) {
           $comment->user = $comment->user;
       }
       return $item->comments;
   }

   public function store(Item $item, Request $request)
   {
       $comment = new Comment();
       $comment->text = $request->comment;
       $comment->user_id = Auth::id();
       
       $item->comments()->save($comment);
   }

   public function update(Item $item, Comment $comment, Request $request)
   {
       if(Auth::id() == $comment->user_id) {
           $comment->text = $request->text;
           $comment->save();
       }
   }

   public function destroy(Item $item, Comment $comment)
   {
       if(Auth::id() == $comment->user_id || Auth::id() == $item->user_id) {
           $comment->delete();
       }
   }
}

ルート設定

Route::get('/items/{item}/get_comments', 'CommentController@get_comments')->name('get_comments');
Route::resource('items.comments', 'CommentController', [
 'only' => ['store', 'update', 'destroy'],
]);

get_commentsもresourceの中に含める書き方ってあるんですか?もしある場合は教えてください〜!

コンポーネント設定(作成)

blade側はitemのshowです。

<comment :item_id="{{$item->id}}" class="mt-5"></comment>

まずは作成から。

<template>
 <div class="">
   <h6>コメント</h6>
   <input type="text" v-model="text" class="px-2 py-2" placeholder="Type a Comment" />
   <button v-show="text != ''" @click.prevent="send()" type="button" class="btn btn-sm btn-primary">送信する</button>
 </div>
</template>

<script>
export default {
 props: ['item_id'],
 data() {
   return {
     text: ''
   }
 },
 methods: {
   send() {
     const text = {
       comment: this.text
     }
     const id = this.item_id
     const array = ["/items/",id,"/comments"];
     const path = array.join('')
     this.text = ''
1    axios.post(path, text).then(res => {
2      this.$store.dispatch('comment/get_comments', id)
     }).catch(function(err) {
       console.log(err)
     })
   }
 }
}
</script>

1, axios.postでコントローラーに飛ばして作成する。

2, vuexのget_commentsを呼び出し、一覧コメントを更新。

Vuex側

const Comment = {
 namespaced: true,
 state: {
   comments: []
 },
 mutations: {
   comments(state, id) {                               1
     const array = ["/items/",id,"/get_comments"];
     const path = array.join('')
     axios.get(path).then(res => {
       state.comments = res.data
     }).catch(function(err) {
       console.log(err)
     })
   }
 },
 actions: {
   get_comments({commit}, id) {
     commit('comments', id)
   }
 }
}

export default new Vuex.Store({          2
 modules: {
   comment: Comment,
 }
})

1, axiosでコントローラーに飛びuser情報を含んだコメント一覧を取得し、commentsに格納しています。

2, モジュール分けしています。

コンポーネント設定(一覧表示, 削除)

blade側はitemのshowです。別コンポーネントを呼び出します。

<comment-list :item_id="{{$item->id}}" :current_user_id="{{Auth::id()}}" :item_user_id="{{$item->user_id}}" class="mt-5"></comment-list>

一覧表示と削除の説明をします。更新についてはあとでします。

<template>
 <div class="">
   <h5 class="mb-3">コメント一覧</h5>
   <div class="container">
     <div v-for="comment in comments" :key="comment.id">
       <div class="row my-2">
         <small class="text-muted mr-4">{{comment.user.name}}</small>
up1        <div v-if="edit_time && comment.id == edit_comment.id">
           <input v-if="edit_time" type="text" v-model="edit_comment.text" class="px-2 py-2" placeholder="Type a Comment" />
           <button v-if="comment.user_id == current_user_id && edit_comment.text != ''" @click.prevent="update(edit_comment)" type="button" class=" btn btn-primary btn-sm">更新</button>
           <button v-if="edit_time" @click.prevent="back(comment)" type="button" class="btn btn-outline-dark btn-sm ml-1">戻る</button>
         </div>
1        <div v-else>
           <p style="display: contents;">{{comment.text}}</p>
           <button v-if="comment.user_id == current_user_id" @click.prevent="edit(comment)" type="button" class="ml-4 btn btn-warning btn-sm">編集</button>
           <button v-if="comment.user_id == current_user_id || item_user_id == current_user_id" @click.prevent="destroy(comment.id)" type="button" class="ml-1 btn btn-danger btn-sm">削除</button>
         </div>
       </div>
     </div>
   </div>
 </div>
</template>

<script>
import {mapState} from 'vuex'
export default {
 props: ['item_id', 'current_user_id', 'item_user_id'],
 data() {                                           up2
   return {
     edit_time: false,
     edit_comment: {}
   }
 },
 computed: {
   ...mapState("comment", ['comments'])             2
 },
 created() {
   this.getComments()                               3
 },
 methods: {
   getComments() {
     const id = this.item_id
     this.$store.dispatch('comment/get_comments', id)
   },
   edit(comment) {                                  up3
     this.edit_time = true
     this.edit_comment = comment
     this.edit_comment.old_text = comment.text
   },
   update(comment) {                                up4
     const update_comment = {
       text: comment.text
     }
     const id = this.item_id
     const array = ["/items/",id,"/comments/", comment.id];
     const path = array.join('')
     axios.put(path, update_comment).then(res => {
       this.edit_time = false
       this.edit_comment = {}
       this.getComments()
     }).catch(function(err) {
       console.log(err)
     })
   },
   destroy(comment_id) {                                     4
     const id = this.item_id
     const array = ["/items/",id,"/comments/", comment_id];
     const path = array.join('')
     axios.delete(path).then(res => {
       this.getComments()
     }).catch(function(err) {
       console.log(err)
     })
   },
   back(comment) {                          up5
     comment.text = comment.old_text
     this.edit_time = false
     this.edit_comment = {}
   }
 }
}
</script>

<style scoped>
.btn{
 height: 2rem;
}
</style>

1, edit_timeがfalseの時は削除ボタンが表示されます。

2, 先ほど書いたvuexのcommentsを読み込んでいます。

3, vuexに行き、axiosの処理をして、commentsを更新するっていう流れを発火させる。

4, axiosでコントローラーに飛ばし、削除しています。終わった後は3を実行。

コンポーネント設定(更新処理)

先ほどのコードのup1..4までを解説します。

up1, edit_timeがtrueになると編集モードに切り替わります。ここで大事なのはcomment.id == edit_comment.idがある事。これが無いとedit_timeがtrueの場合に全てのコメントが編集状態になってしまいます。編集ボタンが押されたコメントだけを編集状態にする、ということをやってくれています。

up2, edit_timeを設定したり、edit_commentを用意しています。

up3,  編集ボタンを押すとedit_timeをtrueにしたりして編集モードに切り替えます。大事なのは最後の1行で、これはup5の何も変更せずに戻った場合の処理に使われます。

up4, ここは更新ボタンが押された時にaxiosでコントローラーに飛ばし、更新処理しています。作成と違うのはpathにコメントidがあるか無いか。

up5, これが意外と重要。編集モードになってから戻るボタンを押した時に元の値を表示させるための処理になります。これが無いと空になってしまいます。up3で設定しているold_textが使われます。

おまけ

itemとアソシエーションが組めていれば、コメント数も以下のように取得、表示出来ますね!

コメント数{{$item->comments()->count()}}</small></p>

疑問

下記のどちらでも数は表示出来ました。取得する値の形が違うのかなと感じましたが、具体的にどんな違いがあるのでしょうか?また、数を票させる場合はどっちを使用した方が良いっていうのはあるのでしょうか?

もし知ってる人居ましたら、教えてください〜!!

$item->comments()->count()
$item->comments->count()

まとめ

作成、削除、一覧表示は比較的簡単。

一覧表示をする際にuserの情報も格納することで、userの名前や編集の時に役に立つ。

編集モードは戻るボタンの事も考えて実装する必要がある。それ以外は作成と似ている。

ざっとこんな感じでしょうか!編集だけ少し考える量が増えますが、そこまで複雑じゃ無いかと思います。

今回はこんな感じで!

以上!