LoginSignup
31
24

More than 3 years have passed since last update.

RailsでAjaxで「いいね!」機能を実装する。

Last updated at Posted at 2021-01-12

何をしたか

Railsの課題を実施しています。その中で「いいね!」機能をAjaxで作成しましょうというタスクがありました。
実は以前、非同期通信ではない「いいね!」機能は作成したことがありました。

▼その時の記録はこちら
Railsで「いいね!」機能を作る - ①アソシエーションに別名をつける
Railsで「いいね!」機能を作る - ②「いいね!」のcreateアクション
Railsで「いいね!」機能を作る - ③「いいね!」を解除できるようにする

また、別の記事で「いつかAjaxで”いいね!”機能を作ってみたい」と言いつつ、作っていなかったので、作成の手順をノートにまとめたいと思います。

なお、ここではメモ程度に実装手順を紹介しています。実際に詳しい実装の手順は↑上記の記事をご覧ください。

また、実装環境は以下の通りです。

  • Rails 5.2.3
  • Ruby 2.6.0

仕様確認・定義する

「いいね!」機能はアソシエーションの定義に若干ひねりが必要です。今回もまずはDB構造と仕様を確認します。その結果、DB構造と仕様は下記の通りでした。

Image from Gyazo

imagesテーブルは画面外にあります。
※他にもあるテーブルの中の一部を表示しています。

このうち、「自分の投稿にいいね!できない」というのは、下記のように、自分の投稿の時にはビューに「いいね!」ボタンを表示しない、という形で実現しています。

▼自分の投稿の時(編集・削除ボタンを表示)
Image from Gyazo

▼自分以外の投稿の時(いいね!ボタンを表示)
Image from Gyazo

また、like_postsと赤文字で書いてあるのは、この後記載するアソシエーションの別名です。
「アソシエーションの別名って???」という方は、こちらの記事に詳しく説明してあります。

Railsで「いいね!」機能を作る - ①アソシエーションに別名をつける

マイグレーションの作成

では、ここから実装していきます。まずは、マイグレーションファイルを作成します。
usersテーブルとpostsテーブルはそれぞれ作成済という前提です。
likesテーブルは下記のように作成します。

db/migrate/XXXXXXXXXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.references :user
      t.references :post

      t.timestamps
      t.index [:user_id, :post_id], unique: true
    end
  end
end

「いいね!」はlikesテーブルにデータをきちんと入力できれば実現されます。
(どうしてそうなるのかは、こちらの記事をご覧くださいませ^^)
Railsで「いいね!」機能を作る - ②「いいね!」のcreateアクション

また、

t.index [:user_id, :post_id], unique: true

の部分で、同じユーザー・同じ投稿への「いいね!」が投稿できないように、DB側で制御をしています。

アソシエーションの定義

postsuserslikesのモデルファイルに、それぞれ下記のようにアソシエーションを記載しました。

class User < ApplicationRecord
  # ★1
  has_many :posts, dependent: :destroy

  # ★2
  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
end

class Post < ApplicationRecord
  # ★1
  belongs_to :user

  # ★2
  has_many :likes, dependent: :destroy
  has_many :users, through: :likes
end

class Like < ApplicationRecord
  belongs_to :post
  belongs_to :user
end

UserPostの★1と★2の下の2行は、それぞれ

  • ★1 ... ユーザーが投稿したpostに関する定義
  • ★2 ... ユーザーが「いいね!」したpostに関する定義

です。User => Like => Post の流れに、:like_postsとアソシエーションの別名をつけているのがポイントです。

has_many :like_posts, through: :likes, source: :post

これで、

user = User.first
user.like_posts

↑この形で、ユーザーが「いいね!」した投稿一覧が取得できます。

コントローラーの定義

コントローラーの内容は以下の通りです。「いいね!」(create)と「いいね!解除」(destroy)をそれぞれ以下のように定義します。

likes_controller.rb
class LikesController < ApplicationController
  def create
    @post = Post.find(params[:post])
    current_user.like(@post)
  end

  def destroy
    @post = Like.find(params[:id]).post
    current_user.unlike(@post)
  end
end

コード中にあるlikeunlikeはそれぞれ、「いいね!」と「いいね解除」を行う、Userのモデルメソッドです。

Userのモデルメソッドの定義(likeunlike

User.rbには、以下のモデルメソッドを定義します。

models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy

  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
  def own?(object)
    id == object.user_id
  end

  def like(post)
    likes.find_or_create_by(post: post)
  end

  def like?(post)
    like_posts.include?(post)
  end

  def unlike(post)
    like_posts.delete(post)
  end
end

like?ユーザーがすでにその投稿に「いいね!」しているかを判別するメソッドです。この後ビューで使うので載せています。

また、owm?も今回の実装には直接関係がないのですが、この後ビューで使用しているので載せています。対象のオブジェクトの作成者を判別するメソッドです。

like_posts.delete(post)

何気に、like_postsでユーザーが「いいね!」したポスト一覧を取得してdestroyしているのも、ミソかなあと思っています。

ビューの定義(非同期「ではない」実装の場合)

その後、ビューを書いていきます。まずは非同期ではない実装でビューを作成していきます。なお、読みやすさのため、装飾のための要素や機能は省いています。

- @posts.each do |post|
  - if logged_in?  # ログイン確認
    - if current_user.own?(post)  # 所有を確認
      = link_to post_path(post), method: :delete do
        = icon 'far', 'trash-alt' # ゴミ箱アイコン
      = link_to edit_post_path(post) do
        = icon 'far', 'edit' # 編集アイコン
    - else
      - if current_user&.like?(post) # すでにいいね!してるか確認
        = link_to like_path(current_user.likes.find_by(post: post)), method: :delete do
        = icon 'fa', 'heart' # ハート(黒)
      - else
        = link_to likes_path(post: post), method: :post do
          = icon 'far', 'heart' # ハート(白)

logged_in?はGemで生成されている、ログインしているかどうかを判別するメソッドです。

実は、ここまでの実装で、「いいね!」機能自体はできています。「いいね!」ボタンを押して、画面をリロードすると「いいね!」と「いいね解除」がそれぞれ切り替えられているのがわかります。

▼「いいね!」を押してから、リロード(画面外のボタンを押下)すると、アイコンが切り替わっている
Image from Gyazo

「いいね!」を非同期で実現する

さて、ここからが本題です。これら「いいね!」機能を非同期で実現していきます。

リンクをremote: trueにする

まずはリンクをremote: trueにして、通信を非同期通信にします。

- @posts.each do |post|
  - if logged_in?
    - # 省略
    - else # それぞれ、link_toの後ろにremote: trueを追記
      - if current_user&.like?(post)
        = link_to like_path(current_user.likes.find_by(post: post)), method: :delete, remote: ture do
        = icon 'fa', 'heart'
      - else
        = link_to likes_path(post: post), method: :post, remote: ture do
          = icon 'far', 'heart'

Ajax用のビューファイルを作る - (1)ボタンの移動

上記のビューファイルのうち、さらにlikeunlikeのボタンについては、それぞれ後に続くAjaxの処理用に、別のパーシャルに分ます。また、Ajaxの処理の目印となるように、id属性も付与しています。

- @posts.each do |post|
  - if logged_in?
    - # 省略
    - else id="like-button-#{post.id}" # id属性を追記
      - if current_user&.like?(post)
        = render 'likes/unlike_button', post: post # パーシャルへ移動
      - else
        = render 'likes/like_button', post: post # 同上

パーシャルの中身はこちらです。

views/likes/_unlike_button.html.slim
= link_to like_path(current_user.likes.find_by(post: post)), method: :delete, remote: true do
  span.c-icon-button= icon 'fa', 'heart', class: 'fa-lg'
views/likes/_like_button.html.slim
= link_to likes_path(post: post), method: :post, remote: true do
  span.c-icon-button= icon 'far', 'heart', class: 'fa-lg'

Ajax用のビューファイルを作る - (2).js.erbファイルを作る

likescreatedestroyアクションに対応した.js.erbファイルをそれぞれ作成します。

/views/likes/create.js.erb
$("#like-button-<%= @post.id %>").html("<%= j(render 'unlike_button', post: @post) %>")
/views/likes/destroy.js.erb
$("#like-button-<%= @post.id %>").html("<%= j(render 'like_button', post: @post) %>")

.js.erbファイルについては、こちらの記事で解説していますので、よろしければご覧ください。

remote: trueでajaxの投稿をPOSTをするよ。

完成!

非常に簡単なステップでしたが、上記の実装で、いいね!機能が非同期で実装できています:relaxed:
意外にあっさり、簡単ですね!

Image from Gyazo

感想...「いいね!」機能もRailsの機能でAjaxを作るのも実は3回目だったので、どちらもスルスルできてよかったです:relaxed:

31
24
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
31
24