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

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

# 前提条件
### 対象となる読者
- 「いいね機能」の実装において、railsの同期処理では問題なく処理されるが、Ajax通信が上手く行かない

### 開発環境
- ruby 2.5.1
- Rails 5.2.4.1
- mysql  Ver 14.14

### 実装済み機能
- 同期処理で「いいね機能」が正しく処理されていること

###筆者が参考にした記事
- 「いいね機能」同期処理編　
[【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸](https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1)
- 「いいね機能」非同期処理編　[【Rails×Ajax】いいね機能ハンズオン #学習者向け#Ajax](https://qiita.com/naberina/items/c6b5c8d7756cb882fb20)

#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533673/4998c87e-3905-9db3-e577-b149ac9b0dcd.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で指定されているか
こちらはまず、どんなエラー文が出たか確認して見ましょう
<img width="1136" alt="スクリーンショット 2020-01-20 11.21.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533673/5c412940-b128-aec2-6683-4588d794982c.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の投稿です！
ご指摘等ございましたらコメントにてお待ちしております。