LoginSignup
9
13

More than 3 years have passed since last update.

【Rails】いいね機能の実装について

Last updated at Posted at 2020-06-16

経緯

プログラミング歴3ケ月の初心者です。
個人的なアプリを作成した際、非同期通信を用いたいいね機能を導入したのですが、ググったものをそのまま使って実装してしまったので、頭の中を整理する(理解を深める)ために記事を投稿しようと思いました。

また本記事は、当機能の実装にあたり、Deviseというgemをインストールしている想定で書いていきます。

実装

今回は、Userモデル、Postsモデル、Likesモデルを使用します。

マイグレーション

Likeモデルのマイグレーションを作成します。

db/migrate/○○_create_likes.rb
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共にアソシエーションの外部キーとして設定します。

モデル

各モデルでアソシエーションを設定していきます。

models/user.rb
  has_many :posts
  has_many :likes
models/post.rb
  belongs_to :user
  has_many :likes
models/like.rb
  belongs_to :user
  belongs_to :post

userは複数「いいね」が可能ですし、
postも複数「いいね」される事が可能ですので、
両者ともhas_manyでlikeとアソシエーションします。

ルート

config/routes.rb
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が呼ばれるのが分かりますね。

コントローラー

controllers/likes_controller.rb
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の説明を見ると理解していただけると思います。

view/likes/_like.html.haml
- 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は記載しません。

部分テンプレートの呼び出し

○○.haml

#like{ id: @post.id }
  = render "likes/like"

前項で用意した部分テンプレートを呼び出す際は、この記載をしてあげてください。
idをカスタムデータ属性で記載しているのがミソです。

JavaScript

ここが非同期通信のキモになります。

views/likes/create.js.erb
$("#like_<%= @post.id %>").html("<%= j(render 'likes/like') %>");
views/likes/destroy.js.erb
$("#like_<%= @post.id %>").html("<%= j(render 'likes/like') %>");

どちらも全く同じ記載です。
「j」メソッドを使用すると、JSとして見なされるようです。
さっき、誰かがページ移動をストップしてJSを探していましたね。。

いいねボタンを押すと、【like_<%= @post.id %>"】の中身が先ほど設定した部分テンプレートにレンダリングされるようになります。

つまり、ページの更新を行わずとも、部分テンプレートの「posts/like」に、非同期で更新されることになります。

まとめ

最後まで読んで頂きありがとうございました。
恐らく、慣れている方からすると難しく無いのでしょうが、やはり初心者からするとすごく難しいですね…
この記事を参考にされる初学者の皆様が無事に実装できることをお祈りしてます!

今回は難しくて余裕がなかったので、おふざけなしで。。

9
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
13