Rails いいね機能の実装




Rails + ajax でコメントにいいねする機能をつくったのでまとめます。


参考：[railsとjsを使ったお手軽「いいね♡機能」](https://qiita.com/YuitoSato/items/94913d6a349a530b2ea2)
[Railsでいいね機能を実装。Ajaxを使い非同期対応。で](https://qiita.com/shiro-kuro/items/f017dce3d199f06d1dcd)


✴️私の場合ログインしてるユーザーがいいねするわけではなくオープン（誰でもいいねできる）だったので上記記事にかいてあるユーザーとのアソシエーションはありません。
✴️いいねが追加されていくだけで減らす機能はつくっていません。

```
ファイル構成

├── comments
│   ├── _comment_lists.html.erb
│   ├── _form.html.erb 
│   └── create.js.erb
├── likes
│   ├── _like.html.erb
│   └── create.js
```

```route.rb
resources :comments ,:only => [:create] do
  resources :likes, only: [:create]
end
```


##前提

Commentテーブルには`likes_count`カラム(ここでは)がある
Likeテーブルには`comment_id`カラムがある（アソシエーション）
Likeモデルに`counter_cache`というカウントしてくれるRailsの機能を使う記述をする↓

```Like.rb
class Like < ApplicationRecord
  belongs_to :comment, counter_cache: :likes_count
end
```

###コントローラ

```CommentsController.rb
class CommentsController < ApplicationController
protect_from_forgery except: :create

  def create
    @comment = Comment.new(comment_params)
    @comment.article_id =  session[:top_id]
    if @comment.save
       @comments = Article.find(session[:top_id]).comments
       @comments = []
       @comments << @comment
       render :comment_lists
     else
       render :comment_lists
    end
  end

  private

  def comment_params
    params.require(:comment).permit(:name, :email, :content, :article_id)
  end

end
```

``` LikesController.rb
class LikesController < ApplicationController

  def create
    @comment_id = Comment.find(params[:comment_id])
    session[:comment_id] = @comment_id.id
    @like = Like.create( comment_id: session[:comment_id])
    @likes = Like.where( comment_id: session[:comment_id])
    @comment_id.reload   #ここでリロードしなくてもいいように更新処理してくれてる
  end

end
```


##ajax


コメント一覧をeachでまわしてコメントを一つずつを見ていきます。
renderでlikeのviewまで飛びます。
そのときにパーシャルやローカル変数を渡す際は書き方に決まりがあるので注意。
` comment `のIDが`1`の場合`("likes_button_<%= comment.id %>")`は`("#likes_button_1")`となる

```comments/_comment_lists.html.erb
<% if @comments.present? %>
  <% @comments.each_with_index do |comment,i| %>
    <div class="comment_box">
      <div class ="wrap">
        <div class = "wrap-l">
          <div class = "id" id = "id_<%= i+1 %>" ><%= i+1 %></div>
          <%= comment.name %>
        </div>
         <div class = "date"><%= comment.created_at.strftime("%Y/%m/%d %H:%M")%></div>
      </div>
      <div class ="wrap">
        <div class ="content"><%= comment.content %></div>
        <div id="likes_button_<%= comment.id %>">
          <%= render :partial =>'likes/like', :locals => { comment: comment } %>
        </div>
      </div>
    </div>
<% end %>
```

いいねのviewまで来ました。
ここで使っている変数`comment`は↑`_comment_lists.html.erb`で指定したものを使っています。

```likes/_like.html.erb
<%= button_to comment_likes_path( :comment_id => comment.id), id: "like-button", remote: true do %>
<div class="btn-social-circle btn-social-circle">
<%= fa_icon("fas star") %>
  <span>
    <%= comment.likes_count %>
  </span>
</div>
<% end %>
```

`likes_controller.rb`の一行目にかいてある` @comment_id = Comment.find(params[:comment_id])`がここでつかわれている
` @comment_id `が`1`の場合`("#likes_button_<%= @comment_id.id %>")`は`("#likes_button_1")`となる

```likes/create.js
$("#likes_button_<%= @comment_id.id %>").html('<%= j(render :partial =>'like', :locals => { comment: @comment_id }) %>')
```

`_like.html.erb`でのcommentの取得の仕方はローカル変数を指定して使えるようにしていて、`create.js`では`likes_controller.rb`に記述した内容が反映されているようだ。


IDに番号を指定しないと３番目のコメントのいいねボタンを押してもDBにはちゃんと３番目のコメントのいいねに＋１した値が入ってるのに見た目上リロードする前の増えたいいねは一番上のコメントのいいねに反映されてしまうので識別しなきゃいけないことに注意だ。


##番外編

リプライがあったらリプライを表示、なかったらアラートを表示するっていう仕様について
ajaxの中でRailsの文法を使って条件分岐できます


```
      $(function () {

    // <!-- リプ一覧欄開閉 -->
      $('#reply_lists_id_<%= i+1 %>').hide();
      <% if comment.replies.present? %>
        $('#comment_user_<%= i+1 %>').click(function () {
          $('#reply_lists_id_<%= i+1 %>').toggle();
      <% else %>
        $('#comment_user_<%= i+1 %>').click(function () {
          window.alert('リプライがありません。');
      <% end %>
      });

      });
```