Rails いいね機能実装（非同期通信）

## はじめに
今回はSNSアプリでデフォルトと言っていいほど備わっている機能（いいね）の実装方法について記載します。

## 開発環境、前提条件
* AWS Cloud9
* Ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
* Rails 6.1.7.3
* Deviseのインストールが完了していて、ユーザーの新規登録やログインができる状態。
* 投稿機能が実装済み(モデル、コントローラ含め)


## モデルを作成しよう
```ruby ec2-user:~/environment
raisl g model Like user_id:integer post_id:integer
```
"post_id"は投稿（画像含む）に関するID

## モデルに記述しよう
```ruby:models/like.rb
class Like < ApplicationRecord

  belongs_to :user
  belongs_to :post
end
```
このアソシエーションのおかげで、以下のような操作が可能になります。

Likeオブジェクトから、関連するUserオブジェクトやPostオブジェクトにアクセスできます。例えば、like.userやlike.postのように書くことができます。
UserオブジェクトやPostオブジェクトから、関連するLikeオブジェクトにアクセスできます。例えば、user.likesやpost.likesのように書くことができます。

```ruby:models/user.rb
class User < ApplicationRecord
has_many :likes, dependent: :destroy #追記
end
```
```ruby:models/post.rb
class Post < ApplicationRecord
has_many :likes, dependent: :destroy #追記

def liked_by(user) #追記
    likes.exsits?(user_id: user.id)
end
end
```

liked_byメソッドは、liked_byメソッドが引数として与えられたuserオブジェクトが、特定のオブジェクト（たとえば、Post）に対して「いいね」をしているかどうかを判断するためのメソッドです。
likes.exists?(user_id: user.id)は、likesの中でuser_idが引数で渡されたuserのidと一致するものが存在するかどうかを確認しています。存在する場合はtrueを、存在しない場合はfalseを返します。

このメソッドを使用することで、特定のユーザーが特定の投稿に「いいね」をしているかどうかを簡単に判断できます。例えば、以下のように書くことができます。
```ruby:views.html.erb
if post.liked_by(current_user)
  # いいねしている場合の処理
else
  # いいねしていない場合の処理
end
```
このメソッドを使って、いいねの状態に応じてビューの表示を変えることができる。

## コントローラを作成しよう
```ruby ec2-user:~/environment
rails g controller likes
```

## コントローラに記述しよう
```ruby:controller/likes.rb
class LikesController < ApplicationController


  def create
    post = Post.find(params[:post_id])
    likes = current_user.likes.new(post_id: post.id)
    likes.save
    redirect_to posts_path(post)
  end


  def destroy
    post = Post.find(params[:post_id])
    like = current_user.likes.find_by(post_id: post.id)
    if like.destroy
      render json: { status: 'success'}
    else
      render json: { status: 'error' }
  end
end
end
```

記述の説明をします。

* createアクションは新しいいいねを作成するために使われます
* params[:id]からいいねをつける対象の投稿(post)を探し"post"変数に格納します
* 現在のユーザー(current_user)に紐ずく新しいいいね(Like)オブジェクトを作成、"post_id"を作成して"likes"変数に格納
* "likes.save"で新しいいいねをDBに保存
* 最後に投稿の一覧画面にリダイレクトする
＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿
* destroyアクションはいいねを削除するために使われます
* params[:post_id]から、いいねを削除する対象の投稿（Post）を見つけて、"post"変数に格納します。
* 現在のユーザー(current_user)が対象の投稿につけたいいね(Like)オブジェクトを探し"like"変数に格納します
* "like.destroy"でいいねをDBから削除します。成功した場合は"JSON"形式で"success"をかえし、失敗した場合は"error"を返します。

## JSON形式(レスポンス)とは

JSONレスポンスとは、Webサーバーがクライアント（通常はブラウザ）に返すデータ形式の1つで、JSON（JavaScript Object Notation）という軽量なデータ交換フォーマットを使用しています。JSONはテキストベースの形式で、データ構造を表現するためにオブジェクト（キーと値のペア）と配列を使用します。

JSONレスポンスは、主に非同期通信（Ajax）でWebアプリケーションとサーバー間でデータをやり取りする際に使用されます。JSON形式はJavaScriptで簡単に扱えるため、Webアプリケーションの開発でよく利用されています。

## ルーティング設定
```ruby:config/routes.rb
Rails.application.routes.draw do

resources  :likes, only:[:create, :destroy]

end
```

## jQueryを使える様にしよう

```ruby:Gemfile
gem "jquery-rails" #追加
```

いつも通り、、、

```ruby ec2-user:~/environment
bundle install
```

続いて"config/webpack/environment.js"に記述
```ruby:config/webpack/environment.js
const webpack = require('webpack')
environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery',
    })
)
```

次に"app/javascript/packs/application.js"に記述

```ruby:app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("jquery")

import "../stylesheets/application"
```

"app/javascript/packs/likes.js"に記述
```ruby:app/javascript/packs/likes.js
$(function() {
  // いいねボタンがクリックされたら
  $('body').on('click', '.fa-heart', function(){
    #現在の状態を取得
    var active = $(this).hasClass('active');
    #いいねの数の要素を取得
    var count = $(this).siblings('.like-count');
    #いいねの数を取得
    var currentCount = parseInt(count.text());
    #いいねの数を更新
    count.text(active ? currentCount - 1 : currentCount + 1);
    #ハートマークの状態を更新
    $(this).toggleClass('active');

    #いいねが成功したら
    $.ajax({
      url: $(this).data('url'),
      type: active ? 'DELETE' : 'POST',
      dataType: 'json',
      data: {'_method': active ? 'DELETE' : 'POST'}
    })
    .done(function(data) {
      console.log(data);
    })
    .fail(function() {
      console.log("error");
    })
    .always(function() {
      console.log("complete");
    });

    #ボタンのデフォルト動作を無効化
    return false;
  });
});
```


ビューに記述
```ruby:app/viwes/posts/_likes.html.erb
<p>
  <% if current_user.liked_posts.include?(post) %>
    <% like = current_user.likes.find_by(post: post) %>
    <i class="fas fa-heart active" data-url="<%= post_like_path(post, like) %>"></i>
  <% else %>
    <i class="fas fa-heart" data-url="<%= post_likes_path(post) %>"></i>
  <% end %>
  <span class="like-count"><%= post.likes.count %></span>
</p>

#FontAwesome使ってます
```

<% if current_user.liked_posts.include?(post) %>:
現在のユーザー（current_user）が、表示中の投稿（post）に「いいね」をしているかどうかをチェックしています。liked_postsは、ユーザーが「いいね」した投稿のリストを返すメソッドです。include?(post)は、そのリストに表示中の投稿が含まれているかどうかを確認します。含まれている場合は、このブロック内のコードが実行されます。

<% like = current_user.likes.find_by(post: post) %>:
現在のユーザーが投稿に対して作成した「いいね」オブジェクトを取得して、like変数に格納します。

"post_like_path(post, like)"が設定され、いいねの解除に必要なURLが格納されます。

"post_likes_path(post)"が設定され、いいねの作成に必要なURLが格納されます。

"post.likes.count"は、投稿に関連付けられた「いいね」の数を返します。


## いいねにアニメーションをつけてみよう
```ruby:app/assets/stylesheets/application.scss
// いいね、アニメーション

/* ハートマーク */
.fa-heart {
  color: gray;
  cursor: pointer;
}

/* ハートマークホバー時 */
.fa-heart:hover {
  color: pink;
}

/* ハートマーククリック時 */
.fa-heart.active {
  color: pink;
  animation: heart-beat 0.5s;
}

/* いいね数 */
.like-count {
  margin-left: 5px;
  font-size: 12px;
}

/* アニメーション */
@keyframes heart-beat {
  0% {
    transform: scale(1);
  }
  25% {
    transform: scale(1.3);
  }
  50% {
    transform: scale(1);
  }
  75% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}
```

以上で実装の完成です！！
コメント、いいねお待ちしてます👻