10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RailsとAjaxを使ったいいね機能の非同期通信

Last updated at Posted at 2020-07-09

転職活動用に個人アプリを開発中です。
今回、RailsとAjaxを使って、いいね機能の非同期化を行いました。

AjaxではjQueryを使うため、jQueryを使えるようにしておく事前準備が必要です。それは参考記事を見てください。以下の記述はそれが設定済みのうえでの話です。

PFCMASTERいいね機能Ajax.png

実現した機能

・「いいね」ボタンを押すとリロードせずに「いいねを取り消す」に表示が変わる
・「いいね」ボタンを押すとリロードせずにlikesテーブルにデータが1つ追加される
・「いいね」ボタンを押すとリロードせずにいいね数が1つ増える
※その逆もしかり

このコードでうまくいきました

コントローラー(likes_controller.rb)

likes_controller.rb
class LikesController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @like = current_user.likes.build(post_id: params[:post_id])
    @like.save
    @likeCounts = Like.where(post_id: params[:post_id])
  end

  def destroy
    @post = Post.find(params[:post_id])
    @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id)
    @like.destroy
    @likeCounts = Like.where(post_id: params[:post_id])
  end

end

個別の投稿ページ(上記の画像のページ)

show.html.haml
.like
  = render partial: "likes/like", locals: {post: @post}

###部分テンプレート(_like.html.haml)

_like.html.haml
- if user_signed_in?
  - if current_user.already_liked?(post)
    = button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true
  - else
    = button_to 'いいね', post_likes_path(post.id), method: :post, remote: true
.likeCounts
  いいね数:
  = post.likes.count

更新したい部分のビュー(いいねしたとき)

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

更新したい部分のビュー(いいねを取り消すとき)

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

JavaScriptが動く仕組み

link_tobutton_toには:remoteオプション(remote: true)がある。button_toremote: trueを追加することで、js形式のリクエストを送信できるようになる。

= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true

参考:Railsガイド

非同期通信の流れ

1.「いいね」ボタンを押す
2.下記のリンクでlikes#createにjs形式でリクエストが飛ばされる

= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true

3.likesコントローラーのcreateアクションが動き、いいねが保存される。Likeモデルを介してデータベースに追加される(リロードせずに)
4.更新したい部分のページcreate.js.erb,destroy.js.erbがレスポンスとして返される。.html(jQueryのhtmlメソッド)は、.like(likeクラス)の部分をhtmlの後ろの( )内に置き換える役割をはたす。

今回苦労したところ

1.部分テンプレートの理解

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

partialオプション:部分テンプレートの呼び出しを行う。今回はlikesフォルダの_like.html.hamlを呼び出したいのでlikes/likeとなる。

localsオプション:部分テンプレート内で{ }内の左辺が変数として使えるようになる。今回でいうとpostが変数として使えるようになる。右辺の@postは何かというと、右辺の@postが左辺のpostに代入して、それが変数として使えるようになる。右辺の@postはどこからきているかというと、postsコントローラのshowアクションで定義しているので、そこからきている。

posts_controller.rb
def show
  @post = Post.find(params[:id])
  Like.new
end

2._like.html.hamlbutton_toのpathの設定
ずっとこのエラーに悩まされていました。

ActionView::Template::Error No route matches (中略) missing required keys: [:id])

この記事を見つけてようやく下記のように記述して解決できました。

= button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true

解決はしたものの、この部分id: post.likes[0].idがまだちゃんと理解できていません。
1つの投稿に複数のいいねがついていたとして、その最初のいいねのidを取得している?

今考えてみると、確かに1つの投稿に複数のいいねがある場合、「どのいいねを取り消すの?指定してくれないとわからないよ」と言われても無理ないなと思いました。

だとすると、必ずしもcurrent_userの付けたいいねではなく、他の人の付けたいいねを取り消してしまう?
まだ修正する必要があるかもしれません😅

(追記)
Sequel Proで確認したところ、他の人のいいねを取り消してしまうことはなく、ちゃんとcurrent_userのいいねが取り消されていました。

一応いいねを消せることは消せます。
この点が明らかになったらまた追記します。

参考記事

Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう!(いいね機能のデモ付)
【Rails×Ajax】いいね機能ハンズオン
Railsでいいね機能を実装。Ajaxを使い非同期対応。で

10
9
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
10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?