【Laravel】ajaxで非同期処理

## やりたいこと
Laravelで作成した記事投稿アプリなどにおいて、ユーザーが記事をいいねした際に画面遷移や更新などの同期処理ではなく非同期で反映させたい（ユーザーが一つの記事対していいねできるのは一回までにする）

## Table
likesテーブルのmigration
likeがuserとarticleに紐付き、user,articleとlikesが一対多の関係
```php
class CreateLikesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('likes', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE');
            $table->integer('article_id')->foreign('article_id')->references('id')->on('articles')->onDelete('CASCADE');
            $table->timestamps();
            $table->timestamp('deleted_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('likes');
    }
}
```

## Model
articleとlikeのリレーションなど
```Article.php
class Article extends Model
{
    //Likeとのリレーション
    public function likes()
    {
        return $this->hasMany(Like::class);
    }
    //いいねされてるかの判定
    public function isLiked(){
        $like = Like::where('user_id', Auth::id())->where('article_id', $this->id)->first();
        if($like !== null){
            return true;
        }
        return false;
    }
}
```

```User.php
class User extends Authenticatable
{
    use Notifiable;

    //Articleとのリレーション
    public function articles(){
        return $this->hasMany(Article::class);
    }

    //Likeとのリレーション
    public function likes(){
        return $this->hasMany(Like::class);
    }
}
```

```Like.php
class Like extends Model
{
    protected $fillable = [
        'user_id', 'article_id'
    ];
    //Userとのリレーション
    public function user(){
        return $this->belongsTo(User::class);
    }
    //Articleとのリレーション
    public function article(){
        return $this->belongsTo(Article::class);
    }
}
```

## Route
```web.php
Route::post('/article/like/{id}', 'LikesController@like')->name('like');
```

## Controller
簡潔にするためcontrollerに更新などのロジックも書いています
```php
public function like(Request $request){
    $article_id = $request->article_id;
    $article = Article::findOrFail($article_id);
    // 実行ユーザーがいいね済みかどうかの確認
    if($article->isLiked()){
        // いいね済みであれば解除
        $article->likes()->where('user_id',Auth::id())->delete();
    } else {
        // 未いいねであれば登録
        $article->likes()->create(['user_id' => Auth::id()]);
    }
    $likes_count = $article->likes()->count();
    $params = [ 'likes_count' => $likes_count ];
    //カウント後のいいね数をjsonで返却
    return response()->json($params);
}
```

## 記事画面
`detail.blade.php`
記事に紐づくいいね数を表示
```html
<span class="likes">
  <i class="bi bi-hand-thumbs-up like-toggle" article_id="{{ $article->id }}"></i>
  <span class="like-counter">{{$article->likes->count()}}</span>
</span>
```

## Ajax
```public/js/like/like.js
$(function () {
    let like = $('.like-toggle'); //like-toggleのついたiタグを取得し代入。
    let article_id;
    like.on('click', function () {
      let $this = $(this); //this=イベントの発火した要素＝iタグを代入
      article_id = $this.data('article_id'); //iタグに仕込んだdata-review-idの値を取得
      //ajax処理スタート
      $.ajax({
        headers: {
          'X-CSRF-TOKEN' : $('meta[name="csrf-token"]').attr('content')
        },
        url: '/article/like',
        method: 'POST',
        data: {
          'article_id': article_id //いいねされた投稿のidを送る
        },
      })
      //成功した時の処理
      .done(function (data) {
        // 返却されたカウント後のいいね数を表示
        $this.next('.like-counter').html(data.likes_count);
      })
      //失敗した時の処理
      .fail(function () {
        console.log('err');
      });
    });
});
```