見出し画像

Ajax(非同期通信)で「いいね」機能を実装する(Ruby on Rails)

作成中のオリジナルアプリに同期通信(リロードすると反映される)でのいいね機能を実装することは出来たのですが、非同期通信での実装がどうしても出来なくて詰まってしまいました。しかし、何とか実装することが出来ましたのでこちらに書き記します。

非同期通信とは
ページ(ブラウザ)のリロード(更新)無しでもレスポンスが得られるというものです。説明よりもイメージしやすいのが、ツイッターやインスタグラムなどのSNSアプリケーションの投稿に対して「いいね」を押すことできます。その際に、どこか別のページに遷移されることがないことや、ページがリロード(更新)されることなく、そのページに留まったままでいいねが反映される様子を見ることが出来ます。別の例で言いますとGoogle マップもそうです。マップをクリックしてスライドすると、ページ更新をせずともマップを動かして見ることが出来ます。同期通信の場合はデータを反映させるために一度ブラウザを更新させます。いいね機能のように、ページの一部分のみを変化させたい場合、同期通信で行うと効率が良くないですし、ユーザー側から見ても一々ブラウザをリロードされたら、あまり快適に感じないと思います。ツイッターのように次から次へと投稿に対していいねを押すことが出来るというのは、非同期通信が行われているということになります。今回は「Ajax」というJava Scriptによる非同期通信の手法でRailsアプリケーションによる「いいね」機能を実装して行きます。

アプリの挙動(例)
https://gyazo.com/747d771169fdecae687e890d2c74b07b
わかりにくいとは思いますが、ブラウザのリロードなしで「いいね」と「いいねを外す」というように表示が切り替わっていることが確認できます。また「いいね」数のカウントも出来ています。

前提
いいね機能を実装するにあたって、Userモデルとusersテーブルが存在すること(ユーザー管理のモデルとテーブルの名前は任意です。例ではUserとしています)。そしていいねを押すための投稿のモデルとテーブルが必要になります(例ではPostモデルとpostsテーブルです)。前提としてアプリケーションにUserモデルとPostモデルが存在し、投稿をDBに保存できて、ビューに反映されているものとします。

参考
いいね機能を実装するにあたり以下のサイトを参考にしました。
【Rails×Ajax】いいね機能ハンズオン
https://qiita.com/naberina/items/c6b5c8d7756cb882fb20
【Rails】いいね機能完全版!同期いいね、いいね数の表示、非同期いいね、アイコン表示、それぞれの実装方法についてまとめて解説
https://techtechmedia.com/favorite-function-rails/

実装
それでは実装して行きます。いいねをするにあたって、いいねを押すのはユーザーになります。そのため投稿に対していいねを押すためには、いいねの情報を保存するテーブル(例ではlikesテーブル)にユーザー(user_id)を紐付ける必要があります。そして「いいね」が押されるのは投稿されたもの対してです。同様にlikesテーブルに対して投稿(post_id)を紐づける必要があります。すなわち、いいね機能を実装するにあたり、ユーザーと投稿の二つの情報が存在しなければなりません。そのためこれら二つの情報を保存するため例としてLikeモデルを作成します。以下のコマンドを入力してください。

rails g model Like user_id:integer post_id:integer
rails db:migrate

そして各テーブルに対してアソシエーションを記述します。

app/models/user.rb

class User < ApplicationRecord
 # Include default devise modules. Others available are:
 # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
 devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :validatable

 has_many :posts
 has_many :likes
end
app/models/post.rb

class Post < ApplicationRecord
 belongs_to :user
 has_many :likes
end
app/models/like.rb

class Like < ApplicationRecord
 belongs_to :user
 belongs_to :post
end

アソシエーションのポイントとしては、ユーザーと投稿はそれぞれ沢山のいいねを保持できること。いいねは、それぞれのモデルに所属するという関係性になります。アソシエーションの記述が出来ましたら、次はメソッドを書いて行きます。
likesテーブルにpost_idが存在していれば、いいねを解除する、存在しなければいいねが出来るというような条件分岐のメソッドをユーザーモデルに定義します。

app/models/user.rb

class User < ApplicationRecord
 # Include default devise modules. Others available are:
 # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
 devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :validatable

 has_many :posts
 has_many :likes
 
 # 以下を追記する
 def liked_by?(post_id)
   likes.where(post_id: post_id).exists?
 end
end

そして次はコントローラーにメソッドを書いて行きます。以下のコマンドを実行します。

rails g controller likes

作成したlikesコントローラーを以下のように編集します。

class LikesController < ApplicationController
 before_action :post_params
 def create
   Like.create(user_id: current_user.id, post_id: params[:id])
 end
 
 def destroy
   Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
 end
 
 private
 
 def post_params
   @post = Post.find(params[:id])
 end
end

投稿のidを取得するためbefore_actionでpost_paramsというメソッドを実行させます。ビューに定義した@postというインスタンス変数を使用し、非同期通信でビューにデータを反映させるためです。
そして最後にルーティングを記述します。以下のように編集します。

config/routes.rb

Rails.application.routes.draw do
 (省略)
 post 'like/:id' => 'likes#create', as: 'create_like'
 delete 'like/:id' => 'likes#destroy', as: 'destroy_like'
end

スクリーンショット 2021-06-03 13.48.43

asオプションを使用することで上記画像のようにPrefixのpathの名前を任意のものに指定することが出来ます。
ここまでで、非同期通信(Ajax)でのいいね機能の実装準備が出来ました。ここからはAjaxで実装を進めて行きます。Java ScriptでHTMLを書き換えたい部分を、部分テンプレートとして切り出します。まだビューの部分は記述していないのでこれから記述します。例としてindex.html.erbを以下のように編集します。投稿の表示を記述してある部分に以下を追加します。

app/views/posts/index.html.erb

<% @posts.each do |post| %>
 # ブロック変数postが使用できる範囲内での任意の位置に以下を記述してください
 <div id="likes_buttons_<%= post.id %>">
   <%= render partial: 'likes/like', locals: {post: post} %>
 </div>
<% end %>

idを付与することにより、どの投稿に対していいねが押されたのかを判別し、HTMLを切り替えられるようにします。
そして部分テンプレートを記述します。コントローラー作成時に併せて作成されたlikesディレクトリに_like.html.erbを作成し以下のように編集します。

app/views/likes/_like.html.erb

<% if user_signed_in? %>
 <% if current_user.liked_by?(post.id) %>
   <td><%= link_to 'いいねを外す', destroy_like_path(post), method: :DELETE, remote: true %> <%= post.likes.count %></td>
 <% else %>
   <td><%= link_to 'いいね', create_like_path(post), method: :POST, remote: true %> <%= post.likes.count %></td>
 <% end %>
<% end %>

ユーザーがサインインしていれば、いいねを押すことが出来るようにしたいので冒頭で、「if user_signed_in?」と記述しています。2行目の「current_user.liked_by?」で先ほどユーザーモデルに記述したメソッドを呼び出しています。こちらでいいねの有無を判別しています。そして、 「post.likes.count」でいいね数を表示しています。またそれぞれに「remote: true」を付与することによってレスポンスの形式をJava Scriptに変更することが出来ます。これによりそれぞれレスポンスとして返却されるビューファイルが「create.js.erb」,「destroy.js.erb」へと変更されます。それではそれぞれのファイルを作成し、非同期通信が出来ているかアラートを表示させて確認します。以下のように編集します。

app/views/likes/create.js.erb

alert('いいねが出来ている');
app/views/likes/destroy.js.erb

alert('いいねを解除している');

結果、以下のGifのようにアラートの表示が出来ていれば成功です。
https://gyazo.com/3425a0c2cad2a4caec62a75d7dd32808

そしてここからはjava scriptライブラリである「jQuery」を用いてHTMLを切り替えられるようにします。jQueryをrailsにインストールします。

Gemfile

gem 'jquery-rails'

「bundle install」を実行します。そしてapplication.jsに「//= require jquery」を追記します。

app/javascript/packs/application.js

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

# 以下を追加する
//= require jquery
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

これでjQueryの導入が完了しました。「create.js.erb」と「destroy.js.erb」を以下のように編集します。

app/views/likes/create.js.erb

$('#likes_buttons_<%= @post.id %>').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");
app/views/likes/destroy.js.erb

$('#likes_buttons_<%= @post.id %>').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");

以上でAjaxによるいいね機能実装が完了しました。最後にFont Awesomeを用いてアイコン装飾を施してみましょう。

アイコン装飾
Font Awesomeに関する説明は割愛します。気になる方は公式ページにアクセスしてください。https://fontawesome.com/
Font Awesomeの導入にはjQueryの時と同様にGemfileに記述してアプリケーション自体にインストールすることも可能ですが、今回はCDNを使用します。application.htmlのheadタグ内に以下を貼り付けてください。

app/views/layouts/application.html

<!DOCTYPE html>
<html>
  <head>
    (省略)
    # 以下を追加する
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css" integrity="sha384-Bfad6CLCknfcloXFOyFnlgtENryhrpZCe29RTifKEixXQZ38WheV+i/6YWSzkz3V" crossorigin="anonymous">
  </head>

  <body>
    (省略)
  </body>
</html>

そして_like.html.erbを以下のように編集しましょう。

app/views/likes/_like.html.erb

<% if user_signed_in? %>
 <% if current_user.liked_by?(post.id) %>
   <td>
     <%= link_to destroy_like_path(post), method: :DELETE, remote: true do %> # 「do」を追加
       <i class="fa fa-heart unlike-btn"></i> # 追加
     <% end %> # 追加
     <%= post.likes.count %>
   </td>
 <% else %>
   <td>
     <%= link_to create_like_path(post), method: :POST, remote: true do %> # 「do」を追加
       <i class="fa fa-heart like-btn"></i> # 追加
     <% end %> # 追加
     <%= post.likes.count %>
   </td>
 <% end %>
<% end %>

<%= link_to (省略) do %>~~~<% end %>と記述すると「~~~」の部分をリンク化することが出来ます。そしてlikes.scssを編集しCSSを与えます。

app/assets/stylesheets/likes.scss

.like-btn {
 font-size: 15px;
 color: #808080;
}

.unlike-btn {
 font-size: 15px;
 color: #e54747;
}

結果、以下のようなGifの挙動になれば成功です。
https://gyazo.com/73306dee24ecc23500568c286910f928
これで今回のAjaxによるいいね機能実装の解説を終了します。アイコンによる装飾はかなり大雑把なもので紹介しましたが、各自で自由にカスタマイズしてみて下さい。非同期通信をするにあたり、実装の手順が多いかと思いますが、しっかりと流れを理解すれば思いの外簡単に実装することが出来るかなと感じます。以上です。ありがとうございました。

この記事が気に入ったらサポートをしてみませんか?