はじめに
初学者のアウトプット記事です。
ご指摘箇所ありましたら、どうぞよろしくお願いします。
今回はですね〜〜〜はい。
イイねボタン非同期実装
某スクールの最終課題にて某メル○リのコピーサイトをチーム開発してます。
今回、自分が担当したイイネボタン実装についての備忘録を残したいと思います。
まず
イイねの仕組みはprogateのrailsコースで丁寧に解説してあったのでそちらで学びながら非同期無しでのイイね実装をしました。
それをふまえたうえで、非同期でのイイね実装です。
以下コードです。
JSファイル記述しません。
js.erbです。
ルーティング
# パスがlikeとlikesで似ていて分かりにくかったのでas: 'like' as: 'unlike'と記述してパス名を変更(わかりやすくしました)
resource :likes, to: 'likes#create', path: 'likes/:product_id/create', only: :create, as: 'like'
resource :likes, to: 'likes#destroy', path: 'likes/:product_id/destroy', only: :destroy, as: 'unlike'
as: likeの記述でパス名を変更するのは分かりやすくしたいだけであって必須ではありません。
モデル
class Like < ApplicationRecord
belongs_to :user
belongs_to :product, counter_cache: :likes_count
validates :product_id, presence: true
validates :user_id, presence: true
end
# counter_cache:で :likes_countに保存
counter_cacheはアソシエーション組んだ子モデルの数を親モデルのカラムに保存。
likesテーブルのレコードが増えたら、productテーブルのlikes_countの数が増えてく仕組みです。
コード記載はしてませんが、
productモデル、userモデル側にはhas_manyの記述でアソシエーションを組んであげてください。
コントローラー
class LikesController < ApplicationController
before_action :set_variables #set_variablesが読み込まれる
def create #イイねがつけられる(イイねのレコードがクリエイトされる)
@like = Like.create(user_id: session[:user_id], product_id: params[:product_id])
@likes_btn = Like.find_by(user_id: session[:user_id], product_id: @product_like.id)
@product_like.reload #リロードする。
end
def destroy #削除する(レコード削除)
@like = Like.find_by(user_id: session[:user_id], product_id: params[:product_id])
@like.destroy
@likes_btn = Like.find_by(user_id: session[:user_id], product_id: @product_like.id)
@product_like.reload
end
def set_variables
@product_like = Product.find(params[:product_id])
end
end
# @likes_btnはページ読み込みの判定する時のif文で使用します。likeカウントがあるかどうか。
like(いいね)のレコードを生成し、削除する動作です。
こちらは↓productコントローラー。ビューページ読み込みの時のshowアクションです。
before_action :set_product
def show
@likes = Like.find_by(user_id: session[:user_id], product_id: @product.id)
end
def set_product
@product = Product.find(params[:id])
end
# こちらの@likesはページアクセスしたり更新した時に読み込まれます。
ビュー
haml表記です。
.product-detail__item__button-box
.product-detail__item__button-box__left
= render partial: 'likes/like', locals: { product: @product, likes: @likes}
# renderで部分テンプレートを呼び出します。
product_controllerの方で定義したインスタンス変数をそれぞれテンプレートに送ります。
%button.product-detail__item__button-box__left__good
.product-detail__item__button-box__left__good__text
- if user_signed_in? #if文でログインしてるか判定。ログインしてないとイイね押せない。
- if likes #いいねのレコードがあるかどうか判定。あるなら削除。ないなら生成。
%i.fas.fa-heart.product-detail__item__button-box__left__good__text__heart
= link_to("いいね!", unlike_path(product.id), method: :delete, remote: true) #ここのremote: true記述でajax通信に切り替わる
%span.product-detail__item__button-box__left__good__count
= product.likes_count
- else
%i.far.fa-heart.product-detail__item__button-box__left__good__icon
= link_to("いいね!", like_path(product.id), method: :post, remote: true) #remote: trueの記述
%span.product-detail__item__button-box__left__good__count
= product.likes_count #イイね数
- else
%i.far.fa-heart.product-detail__item__button-box__left__good__icon
いいね!
%span.product-detail__item__button-box__left__good__count
= product.likes_count
こちらの部分テンプレート内でのlikesとproductはproduct_controllerとlikes_controllerで定義した@product、@product_likeがproductとして、@likes、@likes_btnがlikesとして使えます。
こちら参考にしました。
Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう!(いいね機能のデモ付)
js.erbファイル
ファイルはないのでビューのlikesディレクトリの中に自分で作って記述してください。
$(".product-detail__item__button-box__left").html('<%= escape_javascript(render("likes/like", product: @product_like, likes: @likes_btn)) %>');
クラス名を指定してhtmlをさしかえるってやつですね。
createアクションの非同期時に読みこまれます。likesコントローラーで定義したインスタンス変数を渡します。
以下はdestroyアクションで。同じく。
$(".product-detail__item__button-box__left").html('<%= escape_javascript(render("likes/like", product: @product_like, likes: @likes_btn)) %>');
無事イイねボタンができました。
今回ハマった時に自分は各コントローラーでインスタンス変数の定義ができてなくて値の受け渡しができてませんでした。それによって非同期しなかったり、reloadの記述がなくてカウントだけ非同期しなかったりといったことがありました。動かない時は値の受け渡し、アクション毎にビューが読み込まれるのを考えてみるといいかもです。
js.erb.hamlでも書けるようです。
以上になりました。