転職活動用に個人アプリを開発中です。
今回、RailsとAjaxを使って、いいね機能の非同期化を行いました。
AjaxではjQueryを使うため、jQueryを使えるようにしておく事前準備が必要です。それは参考記事を見てください。以下の記述はそれが設定済みのうえでの話です。
実現した機能
・「いいね」ボタンを押すとリロードせずに「いいねを取り消す」に表示が変わる
・「いいね」ボタンを押すとリロードせずにlikesテーブルにデータが1つ追加される
・「いいね」ボタンを押すとリロードせずにいいね数が1つ増える
※その逆もしかり
このコードでうまくいきました
コントローラー(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
個別の投稿ページ(上記の画像のページ)
.like
= render partial: "likes/like", locals: {post: @post}
###部分テンプレート(_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
更新したい部分のビュー(いいねしたとき)
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");
更新したい部分のビュー(いいねを取り消すとき)
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");
JavaScriptが動く仕組み
・link_to
やbutton_to
には:remoteオプション(remote: true
)がある。button_to
にremote: 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.部分テンプレートの理解
$('.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アクションで定義しているので、そこからきている。
def show
@post = Post.find(params[:id])
Like.new
end
2._like.html.haml
のbutton_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を使い非同期対応。で