Rails6でいいね機能【非同期通信】


はじめに

こんにちわ!プログラミング初心者のものです!
今回はめちゃめちゃ大変だったいいね機能の非同期通信を自身の備忘録と理解を深めるために投稿します!
初心者のため書き間違え等あると思いますがその時はコメント等で教えていただけると幸いです!

さて、今回は凄くシンプルないいね機能の実装をしました。
内容はハートを押すと色付きのものに変わるというもの。
いいねの数などは付けなかったので機能的には不十分かもしれないけど、見た目的にはいいよ!って感じです。

今回機能実装にあたり下記の記事を参考にさせていただきました!
railsとjsを使ったお手軽「いいね♡機能」

前提条件

・Railsのバージョンは6.0.0を使っていること。
バージョンの違いで記述が変わってくる所があるので、注意が必要です!
実際ここを気づかなかったせいでとても時間食いました。
他のバージョンを使用している場合は他の記事で同じ開発環境の方を探してみるのがいいかも知れません。
・deviseを使っていること
ユーザー機能の実装の為、deviseを導入している前提で書きます!
・user/postのコントローラー、モデル、テーブルは作成済み
今回は既に作成されてあるアプリに追加機能でいいね機能を実装する流れで進みます。
作成するアプリは様々だと思いますが、今回は写真投稿型のアプリを例としているため、ユーザー機能、投稿機能は作成されている前提で説明をしています。

準備


今回は非同期通信でいいねをするため、JavaScriptのライブラリであるjQueryを使用します。

1.Gemの導入

jQueryを使用する為に、Gemを導入しましょう。
Gemfileに下のコードを書きます。

gem 'jquery-rails'

ターミナルでbundle installを忘れずに!
僕は癖でサーバーの再起動もついでにやります!

その後アプリにjQueryを読み込む為のコードを追加します!
個人的にここが一番重要です!
ここが無いと非同期になりません!

application.html.erb
//<head>内の一番下に//
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

2.likesテーブル、コントローラーの作成

いいね機能の実装にはuserとpostをつなぐ中間テーブルを作成する必要があります。
どのユーザーがいいねをしているか、どの投稿に対していいねをしているかの情報が必要だからです。
今回はlikesという名前で作成していきます。
ターミナルでモデルを作成したら、マイグレーションファイルを編集していきます。

class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.integer :post_id
      t.integer :user_id

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

user_id、post_idを作成します。カラムの型はinteger型で作成します。
ターミナルでmigrateしたら、モデルファイルにアソシエーションを設定します。

models/like.rb
class Like < ApplicationRecord
  belongs_to :post
  belongs_to :user

  validates_uniqueness_of :post_id, scope: :user_id
end

user/postモデルとは
user/postモデル 1 対 多 likeモデル
の関係です。
user/postモデル側にはhas_manyでlikesを追加しましょう。
また、dependent: :destroyを付けると、投稿が削除された時にいいねも一緒に削除することができます。
そして、postモデルにはメソッドを定義します。

models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy

 def like_user(user_id)
    likes.find_by(user_id: user_id)
   end
end

このメソッドは後々使いますが、簡単に言うとユーザーが既にいいねをしているかどうかを判別する時に使うメソッドです。

続いてコントローラーです。
ターミナルでコントローラーを作成したら以下のように編集してください。

likes_controller.rb
class LikesController < ApplicationController
  before_action :set_post, only: [:create, :destroy]

  def create
    @like = Like.create(user_id: current_user.id, post_id: params[:post_id])
  end

  def destroy
    like = Like.find_by(user_id: current_user.id, post_id: params[:post_id])
    like.destroy
  end

  private

  def set_post
    @post = Post.find(params[:post_id])
  end
end

アクションはcreate,destroyのみです。
createアクションにて@likeにuser_idとpost_idを渡してあげます。
destroyアクションはcreateアクションをそのまま消すって感じですね。
人によってはfind_byではなくfindになる場合があります。

今回は可読性向上の為に before_actionにてプライベートメソッドを呼び出しています。
どの投稿か@postにpost_idを渡してあげます。

3.ルーティングをネストさせる

likesコントローラーをpostsコントローラーにネストさせ、親子関係を表現します。

routes.rb
resources :posts do
    resources :likes, only: [:create, :destroy]
  end

ネストさせたら一度ターミナルでrails routesで一度確認をしましょう。

本番

ここからが本番。
今回はいいねをしているときとしていないときの条件式を部分テンプレートを使って作ります。

1.部分テンプレートの作成

まず、views/likesディレクトリに部分テンプレートファイルを作成します。

今回の設定ではログイン状態でなければいいねボタンが表示されないようにしてます。
そしてログイン状態のユーザーの場合はそのユーザーが既にいいねをしているかどうかをlike_userメソッドを使って判断します。

_like.html.erb
<% if user_signed_in? %>

  <% unless @post.like_user(current_user.id).blank? %>
    <%= link_to post_like_path(post_id: @post.id, id: @post.likes[0].id), method: :delete, remote: true do %>
      <div class="vertical_like">
       <%= image_tag "icon_red_heart.png", size: '40x40' %>
      </div>
    <% end %>
  <% else %>
    <%= link_to post_likes_path(@post.id), method: :post, remote: true do %>
      <div class="vertical_like">
        <%= image_tag "icon_heart.png", size: '40x40' %>
      </div>
    <% end %>
  <% end %>
<% end %>

重要な点はlink_toのパスの引数の部分です。ここで使っているインスタンス変数をコントローラー側でしっかりと指定していないとエラーになります。
imgタグで使用しているアイコンは適宜、自分の表示させたい画像を指定させてください。

2.JavaScriptファイル作成

続いてJavaScriptを作成していきます。
部分テンプレートと同じディレクトリで作成しましょう!

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

それぞれ一行だけですね!
とても簡単です。

3.いいねを表示させたい所で呼び出す

そしていいねを呼び出したい場所で

 <div id="likes_buttons<%= @post.id %>">
   <%= render partial: 'likes/like', locals: { post: @post, like: @like} %>
 </div>

これで実装完了です!
今回はシンプルにアイコンだけ非同期で動くいいね機能を実装してみましたが、
終わってみればコードは簡単そうに見えますがエラーの地獄でした笑
インスタンス変数が違ったり、スペルミスがあったりと一つ抜けると動かないような感じでしたね。
解説が苦手なので、わかりにくい部分が多いかもしれませんが、いいね機能の記事はたくさんあるので複数参考にすると理解が深まりまると思います!

プログラミング初心者はこの機能を実装してみたい人が多い気がするので、少しでも参考になればと思います!
動くと感動するので実装させたい人は頑張ってください!!