LoginSignup
8
15

More than 3 years have passed since last update.

【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け

Last updated at Posted at 2020-01-20

目的

はじめまして。
今回、Railsでいいね機能をQiita等の記事通りに行っても上手く行かない!という方へ向けた、ちょっとした実装の際のチェック項目を列挙させていただきます。

前提条件

対象となる読者

  • 「いいね機能」の実装において、railsの同期処理では問題なく処理されるが、Ajax通信が上手く行かない

開発環境

  • ruby 2.5.1
  • Rails 5.2.4.1
  • mysql Ver 14.14

実装済み機能

  • 同期処理で「いいね機能」が正しく処理されていること

筆者が参考にした記事

Ajax処理のいいね機能の実装方法のチェック項目

ずばり、先に結論をここで提示させていただきます

1. 部分テンプレートの呼び出しが正しく相対パスで指定されているか

2. インスタンス変数の指定がfavorites_controller.rbで指定されているか

の2点です。では、詳しく見ていきましょう。

1. 部分テンプレートの呼び出しが正しく相対パスで指定されているか

よく陥りがちなミスの一つですね。実際にどのように間違えて実装しどの様なエラー文が出たのでしょうか?

view/items/show.html.haml
.btn-bar
  .btn-box
    = render partial: "favorite_ajax", locals: { item: @item }
-# view/items/_favorite_ajax.html.hamlでいいねボタンを部分テンプレートを作成した

view/items/_favorite_ajax.html.haml
- if user_signed_in? -# ユーザーがログインしているか判断
  - if item.favorited_by?(current_user) -# ログイン中のユーザーがいいねしているかしていないかを判断
    = link_to item_favorites_path(item.id), method: :delete, class: "favorite red", remote: true do -# リクエストをjs形式で送信
      = icon('fas', 'heart')
      いいね!
      = item.favorites.count
  - else
    = link_to item_favorites_path(item.id), method: :post, class: "favorite", remote: true do -# リクエストをjs形式で送信
      = icon('far', 'heart')
      いいね!
      = item.favorites.count
- else
  = link_to new_user_session_path, class: "favorite", remote: false do -# リクエストをhtml形式で送信
    = icon('far', 'heart')
    いいね!
    = item.favorites.count
view/favorites/create.js.haml(失敗例)
$('.btn-box').html("#{escape_javascript(render partial: "favorite_ajax", locals: { item: @item })}");
-# この記述ではview/favorites/_favorite_ajax.html.hamlを呼び出していることとなる。従って、対応するファイルが無いことからTemplate::Error(Missing partial)が発生
view/favorites/destroy.js.haml(失敗例)
$('.btn-box').html("#{escape_javascript(render partial: "favorite_ajax", locals: { item: @item })}");
-# この記述ではview/favorites/_favorite_ajax.html.hamlを呼び出していることとなる。従って、対応するファイルが無いことからTemplate::Error(Missing partial)が発生

エラー文
スクリーンショット 2020-01-20 10.40.39.png

items_controller.rbのshowアクションのビューでいいね機能の実装をしています。また、いいね機能のDBへの保存・削除はfavorites_controller.rbのcreateアクション・destroyアクションで実装をしています。

今回、いいねボタンを押した際にビューが切り替わる部分をview/items/_favorite_ajax.html.hamlで切り出し部分テンプレートを作成しました。ajaxではview/favorites/create.js.haml, view/favorites/destroy.js.hamlをそれぞれ用意し、view/items/_favorite_ajax.html.hamlを呼び出したかったのですが、相対パスの指定が誤っていました。以下のように修正するとTemplate::Error(Missing partial)は解消されます。

view/favorites/create.js.haml
$('.btn-box').html("#{escape_javascript(render partial: "items/favorite_ajax", locals: { item: @item })}");
-# partial: にitems/ を追加
view/favorites/destroy.js.haml
$('.btn-box').html("#{escape_javascript(render partial: "items/favorite_ajax", locals: { item: @item })}");
-# partial: にitems/ を追加

2. インスタンス変数の指定がfavorites_controller.rbで指定されているか

こちらはまず、どんなエラー文が出たか確認して見ましょう
スクリーンショット 2020-01-20 11.21.25.png
renderの中身のitem.favorited_by?に対して

undefined method `favorited_by?' for nil:NilClass

とエラーが出ています。ここでいうitemとはitems_controller.rbのshowアクションで定義されているインスタンス変数@itemをrenderの中身ではitemとして記述している、という意味です。favorited_by?については、item.rbで事前に定義した「ログイン中のユーザーがいいねしているかしていないかを判断」するメソッドです。

models/item.rb
class Item < ApplicationRecord
# (中略)
  def favorited_by?(user)
    favorites.where(user_id: user.id).exists?
  end
end

このことから、
 render内ではitem.favorited_by?が定義されていない
→ render内ではitemそのものが定義されていない
view/favorites/destroy.js.hamlでは、@itemが定義されていない
favorites_controller.rbでは、@itemが定義されていない!!

ということが判明しました。確認してみると確かにfavorites_controller.rbでは、@itemが定義されていなかったので、以下のように記述を加えたところ、正しくAjax処理が実行されました。
(items_controller.rbでも同様のset_itemメソッドを定義済みです)

favorites_controller.rb
class FavoritesController < ApplicationController
  before_action :authenticate_user!
# 追記==========================================================================
  before_action :set_item 
# ==============================================================================
  def create
    favorite = current_user.favorites.build(item_id: params[:item_id])
    if favorite.save
    else
      flash.now[:alert] = favorite.errors.full_messages
    end
  end

  def destroy
    favorite = Favorite.find_by(item_id: params[:item_id], user_id: current_user.id)
    if favorite.destroy
    else
      flash.now[:alert] = '削除できませんでした。'
    end
  end

  private
# 追記==========================================================================
  def set_item
   @item = Item.find(params[:item_id])
  end
# ==============================================================================
end

まとめ

いかがだったでしょうか。
いいね機能のAjaxは、実装の手順そのものはすごくシンプルです。しかし、いいね機能専用のビューを用意していなかったり、部分テンプレートの保存場所の違いによって記述内容が異なるケースがあります。当たり前のことではあるのですが、記事通り実装してみて上手く出来なかった時、解決の一助となれば幸いです。

※私自身初めてのQiitaの投稿です!
ご指摘等ございましたらコメントにてお待ちしております。

8
15
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
8
15