経緯
プログラミング歴3ケ月の初心者です。
個人的なアプリを作成した際、非同期通信を用いたいいね機能を導入したのですが、ググったものをそのまま使って実装してしまったので、頭の中を整理する(理解を深める)ために記事を投稿しようと思いました。
また本記事は、当機能の実装にあたり、Deviseというgemをインストールしている想定で書いていきます。
実装
今回は、Userモデル、Postsモデル、Likesモデルを使用します。
マイグレーション
Likeモデルのマイグレーションを作成します。
class CreateLikes < ActiveRecord::Migration[5.0]
def change
create_table :likes do |t|
t.references :post, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
user_id、post_id共にアソシエーションの外部キーとして設定します。
モデル
各モデルでアソシエーションを設定していきます。
has_many :posts
has_many :likes
belongs_to :user
has_many :likes
belongs_to :user
belongs_to :post
**userは複数「いいね」**が可能ですし、
postも複数「いいね」される事が可能ですので、
両者ともhas_manyでlikeとアソシエーションします。
ルート
Rails.application.routes.draw do
devise_for :users
resources :users
resources :posts do
resources :likes, only: [:create, :destroy]
end
end
postsにlikesをネストします。
ネストすることによって、どの投稿(post)にいいね(like)が付いたのか判断できるようになります。
参考までに rails routesをすると…
post_likes POST /posts/:post_id/likes(.:format) likes#create
post_like DELETE /posts/:post_id/likes/:id(.:format) likes#destroy
特定の投稿(post)に紐づけてlikeが呼ばれるのが分かりますね。
コントローラー
class LikesController < ApplicationController
before_action :set_post
def create
@like = Like.create(user_id: current_user.id, post_id: @post.id)
end
def destroy
@like = current_user.likes.find_by(post_id: @post.id)
@like.destroy
end
private
def set_post
@post = Post.find(params[:post_id])
end
end
destroyは、現在ログインしているuserがいいね(like)したものをアソシエーションで取得し、
その中から**いいねを解除する投稿(@post)**を探しています。
ビュー
部分テンプレートの作成
今回は、いいねボタン、いいね数の表示部分については、部分テンプレートで用意します。
理由は、次項のJavaScriptの説明を見ると理解していただけると思います。
- if Like.find_by(user_id: current_user.id, post_id: @post.id)
= link_to "/posts/#{@post.id}/likes/#{@post.likes.ids}", method: :delete, class:"element", remote: true do
= icon("fas", "heart", class: "like")
- else
= link_to "/posts/#{@post.id}/likes", method: :post, class:"element", remote: true do
= icon("fas", "heart", class: "unlike")
.count
= @post.likes.length
既にいいねしている場合はtrue、まだいいねしていない場合はfalseを返すようにしています。
「remote: true do」は非同期通信をする場合のオプションです。
本来はリンク先へページが移動しますが、「remote: true do」を記載すると、代わりにJSを探します。
よって、ページの移動がストップされます。
(値はリンク先に飛ぶようなので、ページは移動しませんが、DBへの登録は行われるようです。)
※「icon("fas", "heart")」を使用するには、font-awesome-sassが必要となります。
※今回、cssは記載しません。
部分テンプレートの呼び出し
#like{ id: @post.id }
= render "likes/like"
前項で用意した部分テンプレートを呼び出す際は、この記載をしてあげてください。
idをカスタムデータ属性で記載しているのがミソです。
JavaScript
ここが非同期通信のキモになります。
$("#like_<%= @post.id %>").html("<%= j(render 'likes/like') %>");
$("#like_<%= @post.id %>").html("<%= j(render 'likes/like') %>");
どちらも全く同じ記載です。
「j」メソッドを使用すると、JSとして見なされるようです。
さっき、誰かがページ移動をストップしてJSを探していましたね。。
いいねボタンを押すと、【like_<%= @post.id %>"】の中身が先ほど設定した部分テンプレートにレンダリングされるようになります。
つまり、ページの更新を行わずとも、部分テンプレートの「posts/like」に、非同期で更新されることになります。
まとめ
最後まで読んで頂きありがとうございました。
恐らく、慣れている方からすると難しく無いのでしょうが、やはり初心者からするとすごく難しいですね…
この記事を参考にされる初学者の皆様が無事に実装できることをお祈りしてます!
今回は難しくて余裕がなかったので、おふざけなしで。。