【Rails6】いいね機能を実装してみた

#環境
Rails6.1.3
Ruby3.0.1

#同期処理
##①モデルを作成
今回はコメントに対するいいねなので、`user_id`と`comment_id`と関連付けます

```
% rails g model Good user_id:integer comment_id:integer
```

##②モデルに関係性を記述
`user,commentモデル`にそれぞれ以下の記述を追加
`dependent: :destroy`でユーザーかコメントが消えたときにいいねも消えるようになります

```ruby
has_many :goods, dependent: :destroy
```

```ruby:app/model/good.rb
  belongs_to :user
  belongs_to :comment
```

##③ルーティングの設定
今回は、コメントに対するいいねということなので、ルーティングがネスト化されます
また、いいねは作るか消すかだけなので、onlyも書いておきます

```ruby:config/routes.rb
  resources :comments do
    resources :goods, only: %i[create destroy]
  end
```
##④controllerの編集
`create`と`destroy`についてメソッドを書きます
いいねをするのは、`current_user`なので、`current_user.id`から`user_id`を取得します

```ruby:app/controller/goods_controller.rb
class GoodsController < ApplicationController

  def create
    @good = Good.create(user_id: current_user.id, comment_id: params[:comment_id])
    @good.save
  end

  def destroy
    @good = Good.find_by(user_id: current_user.id, comment_id: params[:comment_id])
    @good.destroy
  end
end
```
##⑤viewの設定
`<% if logged_in? %>`でログインしているか判定
`<% if current_user.good_user?(comment.id) %>`で現在のユーザーがいいねをしているかどうかを判定し、`create`か`destroy`か分岐


```erb
   <div class="comment_goods">
      <% if logged_in? %>
      <% if current_user.good_user?(comment.id) %>
      <%= link_to comment_good_path(comment.id, current_user.goods.find_by(comment_id: comment.id).id), method: :delete do %>
      <p class="good-button"><i class="far fa-heart like-btn" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= comment.goods.count %></span></p>
       <% end %>
       <% else %>
       <%= link_to comment_goods_path(comment.id), method: :post do %>
       <p class="good-button"><i class="fas fa-heart unlike-btn" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= comment.goods.count %></span></p>
       <% end %>
       <% end %>
       <% end %>
   </div>
```

pathの名前は

```
% rails routes  
```

で確認

```
comment_goods POST   /comments/:comment_id/goods(.:format)                                                             goods#create
comment_good DELETE /comments/:comment_id/goods/:id(.:format)
```

ここから持ってきます

#非同期処理
非同期処理は、Ajax処理とjQueryを利用します。
jQueryの導入方法は、調べたらたくさん出てくるはずなので調べてみてください。
（Railsのバージョンによって方法が変わるので注意！）

##①いいね機能部分を部分テンプレート化

```erb:app/view/goods/_good.html.erb
<% if current_user.good_user?(comment.id) %>
<%= link_to comment_goods_path((comment.id)), method: :delete, remote: true do %>
<p id="good_<%= comment.id %>"><i class="fas fa-heart unlike-btn" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= comment.goods.count %></span></p>
<% end %>
<% else %>
<%= link_to comment_goods_path((comment.id)), method: :post, remote: true do %>
<p id="good_<%= comment.id %>"><i class="far fa-heart like-btn" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= comment.goods.count %></span></p>
<% end %>
<% end %>
```

Ajax処理で更新させたい部分（今回であればいいねのボタン部分）を部分テンプレート化します。
ここで同期処理⑤のViewからの変更点があります。
###remote: true doを追記
`remote: true do`にすることで、js形式のリクエストを送信することができます。
`create.js.erb`,`destroy.js.erb`が発動します。

###ボタンのidを変更
`id="good_<%= comment.id %>"`にすることで、更新されるいいねボタンを限定しています。
これを行わないと、全てのいいねボタンが同時に更新されることになってしまいます。

###パスの変更
以下の記事を参考にさせていただきました。
主キーの所得方法で、めちゃくちゃ悩みました、、、

https://qiita.com/yama_diary/items/038d6a093e8db8e7d991

##②レンダリング先での表示方法
レンダリング先で使用している変数（今回であればcomment)を部分テンプレートにも渡すために、
`locals:{comment:comment}`を記載します。

```erb
<%= render partial:'goods/good',locals:{comment:comment} %>
```

```erb:app/view/questions/show.html.erb
<% @comments.each do |comment| %>
<% if @question.id == comment.question_id %>
<div class="comment-box">
    <% if comment.user.image? %>
    <%= link_to (image_tag comment.user.image.thumb.to_s),user_path(comment.user.id) %>
    <% else %>
    <%= link_to (image_tag "default.png",class: "q-user-image"), user_path(comment.user.id), method: :get,  alt:"ユーザーアイコン" %>
    <% end %>
    <%= comment.user.name %>
    <%= comment.content %>
    <%= time_ago_in_words(comment.created_at) %>前
        <% if logged_in? %>
        <%= render partial:'goods/good',locals:{comment:comment} %>
</div>
```


##③create,destroyのjs処理を追加
いいねボタンが押された時の処理を作成します。
idで指定されたいいねボタンが押されたら、部分テンプレート化されたいいね部分のみ更新する処理になっています。
ここでは、インスタンス変数を使います。

```erb:app/view/goods/create.js.erb
$('#good_<%= @comment.id %>').html("<%= j(render partial: 'goods/good', locals: {comment: @comment}) %>");
```

```erb:app/view/goods/destroy.js.erb
$('#good_<%= @comment.id %>').html("<%= j(render partial: 'goods/good', locals: {comment: @comment}) %>");
```

#終わりに
いいね機能の非同期処理を実装する中で、インスタンス変数とローカル変数の違い、resourceとresoursesの違いなど多くの学びがありました。
わかりにくい部分、間違えている部分などありましたらコメントいただけますと幸いです！