こちらの記事が大変参考になりました。↓
https://note.com/become_engineer/n/n45f7285622e3
備忘録としてメモしておきます。
非同期通信とは
ブラウザのリロード更新無しでもレスポンスが得られるというものです。(例えばツイッターやインスタグラムなどの、投稿に対して「いいね」を押すことできる)その際に、どこか別のページに遷移されることがないことや、ページがリロードされることなく、そのページに留まったままでいいねが反映される子を見ることが出来ます。
「Ajax」というJava Scriptによる非同期通信の手法で「いいね」機能を実装して行きます。
前提
UserモデルとCommentモデルが存在し、投稿をDBに保存できて、ビューに反映されている
実装
likeモデルの作成
rails g model Like user_id:integer post_id:integer
rails db:migrate
各テーブルに対してアソシエーションを記述します。
class User < ApplicationRecord
(省略)
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liked_comments, through: :likes, source: :comment
def already_liked?(comment)
self.likes.exists?(comment_id: comment)
end
end
class Post < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
has_many :liked_users, through: :likes, source: :user
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :comment
validates :user_id, presence: true
validates :comment_id, presence: true
counter_culture :comment
end
いいねをカウントするためにcounter_cultureというgemを使用しています。
※counter_cultureの導入
# いいねcounter
gem 'counter_culture'
bundle install
そもそもcounter_cultureってなに?
Ruby on Railsには、counter_cultureという親レコードが持ってる子レコードの数を測定してカラムに吐き出してくれる便利な機能があります。
counter_cultureができること
・条件に一致した子レコード数を集計
・途中からカウンターを追加して、現在の値を集計
そして次はコントローラーを作成して、メソッドを書いて行きます。
rails g controller likes
class LikesController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :comment_params
def create
Like.create(user_id: current_user.id, comment_id: params[:id])
end
def destroy
Like.find_by(user_id: current_user.id, comment_id: params[:id]).destroy
end
private
def comment_params
@comment = Comment.find(params[:id])
end
end
投稿のidを取得するためbefore_actionでpost_paramsというメソッドを実行させます。ビューに定義した@postというインスタンス変数を使用して、非同期通信でビューにデータを反映させるためです。
そして最後にルーティングを記述します。
resources :comments, only: [:create, :destroy] do
resources :likes, only: [:create, :destroy]
end
post 'like/:id', to: 'likes#create', as: 'create_like'
delete 'like/:id', to: 'likes#destroy', as: 'destroy_like'
Comment に対する”いいね”なので、comments の配下としてパスを設定します。
こうすることで、どの Comment に対する”いいね”なのか id によって判別がつくようになります。
asオプションを使用することで上記画像のようにPrefixのpathの名前を任意のものに指定することが出来ます。
ここまでで、非同期通信(Ajax)でのいいね機能の実装準備が出来ました。ここからはAjaxで実装を進めて行きます。Java ScriptでHTMLを書き換えたい部分を、部分テンプレートとして切り出します。
例として_comment.html.erbを以下のように編集します。投稿の表示を記述してある部分に以下を追加します。
<li id="comment-<%= comment.id %>" data-comment-id="<%= comment.id %>">
<span class="user"><%= link_to comment.user.name, comment.user %></span>
<span class="content"><%= comment.content %></span>
# 上記のcommentが使用できる範囲内での任意の位置に以下を記述
<div id="likes_buttons_<%= comment.id %>">
<%= render "likes/like", comment: comment %>
</div>
</li>
idを付与することにより、どの投稿に対していいねが押されたのかを判別し、HTMLを切り替えられるようにします。
そして部分テンプレートを記述します。likesディレクトリに_like.html.erbを作成し以下のように編集します。
<% if user_signed_in? && !current_user?(comment.user) %>
<span class="like">
<% if current_user.already_liked?(comment) %>
<td>
<%= link_to destroy_like_path(comment), method: :DELETE, remote: true do %>
<i class="fa fa-heart unlike-btn"></i>
<% end %>
<%= comment.likes.count %>
</td>
<% else %>
<td>
<%= link_to create_like_path(comment), method: :POST, remote: true do %>
<i class="fa fa-heart like-btn"></i>
<% end %>
<%= comment.likes.count %>
</td>
<% end %>
</span>
<% end %>
current_user.already_liked?こちらでいいねの有無を判別しています。そして、 「post.likes.count」でいいね数を表示しています。またそれぞれに「remote: true」を付与することによってレスポンスの形式をJava Scriptに変更することが出来ます。これによりそれぞれレスポンスとして返却されるビューファイルが「create.js.erb」,「destroy.js.erb」へと変更されます。それではそれぞれのファイルを作成し、非同期通信が出来ているかアラートを表示させて確認します。以下のように編集します。
alert('いいねが出来ている');
alert('いいねを解除している');
結果、いいねを押したときにアラートの表示が出来ていれば成功です。
最後にFont Awesomeを用いてアイコン装飾をします。
$("#comment-<%= @comment.id %> .like").html("<%= escape_javascript(render "likes/like", comment: @comment) %>");
$("#comment-<%= @comment.id %> .like").html("<%= escape_javascript(render "likes/like", comment: @comment) %>");
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css" integrity="sha384-Bfad6CLCknfcloXFOyFnlgtENryhrpZCe29RTifKEixXQZ38WheV+i/6YWSzkz3V" crossorigin="anonymous">
<% if user_signed_in? && !current_user?(comment.user) %>
<span class="like">
<% if current_user.already_liked?(comment) %>
<td>
<%= link_to destroy_like_path(comment), method: :DELETE, remote: true do %>
<i class="fa fa-heart unlike-btn"></i>
<% end %>
<%= comment.likes.count %>
</td>
<% else %>
<td>
<%= link_to create_like_path(comment), method: :POST, remote: true do %>
<i class="fa fa-heart like-btn"></i>
<% end %>
<%= comment.likes.count %>
</td>
<% end %>
</span>
<% end %>