Help us understand the problem. What is going on with this article?

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

転職活動用に個人アプリを開発中です。
今回、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を使い非同期対応。で

naota7118
30歳未経験からウェブエンジニアへの転職を目指しています。
https://bibiri0925.hatenablog.com/
circleci
デベロッパーが素晴らしいプロダクトを高品質かつ迅速にデリバリーするために、CircleCIユーザーがお互いの技術や知見を交換しながら支え合い、幸せなデベロッパーライフをビルドするためのコミュニティです。
https://circleci.connpass.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away