【Ruby / Rails / devise】お気に入り機能( いいね機能 )の実装方法


## お気に入り機能の作成イメージ

![スクリーンショット 2022-05-24 15.43.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655769/95e9db65-09b4-c636-e30a-3f589ba8a48b.png)

今回はこのようなお気に入り機能の実装をしていきたいと思います。
（非同期通信は使用しないで作成しています）


## 前提

・deviseを導入している
（user_signed_in?メソッドを使う必要があるため）

・userモデル、お気に入り機能をつけたいモデルが存在している
（お気に入り機能をつけたいモデル　→ 今回はitemモデルで作成します）

## 作成手順

### ①お気に入り機能のモデルを作成する

・名前はお好きなものでOK
今回は「favorite」モデルで作成します。
他には「like」モデルで作成している例もあります。
（モデルは単数形で書くルールがありますので、単数形で作成しましょう）

```ターミナル
rails g model favorite
```

以下の２つのファイルができたら成功です。

```
db/migrate/xxxx_create_favorites.rb
app/models/favorite.rb
```

##### ↑ likeモデルにした場合は「favorite」が「like」になります。


### ②カラムを追加


```xxxx_create_favorites.rb
class CreateFavorites < ActiveRecord::Migration[6.0]
  def change
    create_table :favorites do |t|
      t.references :user, foreign_key: true         #追記
      t.references :item, foreign_key: true         #追記

      t.timestamps
      t.index [:user_id, :item_id], unique: true    #追記
    end
  end
end
```
【お気に入りの機能はユーザーと投稿がに紐づいている必要があるため以下を追加します】
・t.references :user, foreign_key: true
・t.references :item, foreign_key: true
↑　２つ目は自分のアプリの投稿モデル（お気に入り機能をつけたいモデル）に変更しましょう

【同じユーザーが同じ投稿に対して、複数回いいね登録できないようにする設定を追加します】
・t.index [:user_id, :item_id], unique: true


```ターミナル
rails db:migrate
```
上記の内容を更新し、データベース（シークエルPROなど）が作成されているか確認しましょう。


### ③モデルにアソシエーションの設定（３点）

```app/models/favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user 
  belongs_to :item 
end
```
```app/models/user.rb
class User < ApplicationRecord
  has_many :items

  #お気に入り機能のアソシエーション処理
  has_many :favorites, dependent: :destroy
end
```
```app/models/item.rb
class Item < ApplicationRecord
  belongs_to :user 

  #お気に入り機能のアソシエーション処理
  has_many :favorites, dependent: :destroy 
end
```

favoriteモデルは中間テーブルとして作成していますが
「through:」表記なしでも実装可能でした。


### ④ルーティングの設定

お気に入り機能をつけたいコントローラーに「ネスト」の形で記述します。

！　resourceメソッド（単数形）で記述します。
　　　・・indexを除いたアクションのルーティングがid無しで生成されるようにするため

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "items#index"

  resources :items do               #こちらの２行を追加
     resource :favorites, only: [:create, :destroy] 
  end
end
```

### ⑤コントローラー作成・記述


```ターミナル
rails g controller favorites create destroy
```

```app/controllers/favorites_controller.rb

class FavoritesController < ApplicationController

  def create
    @favorite = current_user.favorites.create(item_id: params[:item_id])
    redirect_back(fallback_location: root_path )
  end

  def destroy
    @item = Item.find(params[:item_id])
    @favorite = current_user.favorites.find_by(item_id: @item.id)
    @favorite.destroy
    redirect_back(fallback_location: root_path )
  end

end

```


#### 内容

###### current_user.favorites.create(item_id: params[:item_id])
・・現在ログインしているユーザーがお気に入りした「投稿（item）のid」を保存する

###### redirect_back
・・お気に入りをしたページにリダイレクトする（非同期にしない場合実装）

###### (fallback_location: root_path )
・・万が一お気に入りをしたページに戻れない場合は、エラー回避のためトップページに遷移する

###### @item = Item.find(params[:item_id])
・・送られてきた（お気に入りされた）投稿のidを変数に代入、以下の行ではその変数を使用して削除を行っている


### ⑥ビューの作成

初めにビューで使用する自作のメソッドをuserモデルに作成します。

```app/models/user.rb

  def favorited?(item)
    self.favorites.exists?(item_id: item.id)
  end

```

（）内の引数が、お気に入りされているか・いないかを確認しています。

最終的なビューはこちら

・@items = Item.all　（全ての投稿のデータを取得しています）

```app/views/items/index.html.erb
 <% @search_item.each do |items| %>

   <% if user_signed_in? %>

        <% if current_user.favorited?(items) %>
           　 <%= link_to item_favorites_path(items), method: :delete do %>
                <span class="item__favorite">❤️</span>
                <span class="favorite__count"><%= items.favorites.count %></span>
             <% end %>
         　　　　<% else %>
             <%= link_to item_favorites_path(items), method: :post do %>
                <span class="item__favorite">🤍</span>
                <span class="favorite__count"><%= items.favorites.count %></span>
              <% end %>
         　　　　<% end %>

     <% else %>
               <%=link_to '🤍', new_user_session_path, class: :item__favorite%>
        <% end %>

  <% end %>
```

#### 内容
（以下詳しい内容では、記述上「do」を抜いて説明しているものがありますので、以下の記述は参考までにご覧いただけますと幸いです）

```
　　<% if user_signed_in? %>
　　　・・・A
　　<% else %>
　　・・・B
 <% end %>
```

#### A・・いいねを押したり、外したりできる
#### B・・♡を押すとログインページへ遷移する（いいね機能は使えない）

・補足
Bのようにログインページや新規登録ページに遷移することで、「ログイン（新規登録）すると使えること」を感覚的に感じてもらいやすくなったり、登録ユーザーを増やすことにも繋がると思いましたので、今回はリンク先をこちらにしました。


「ユーザーがログインしていたら、Aの処理を実行する」
「ログインしていなければBの処理を実行する」という記述をすることで未ログイン時のエラー防ぐことができます。

こちらの記述をしていないと、未ログイン時にはこのようなエラーが出ました。

![スクリーンショット 2022-05-24 15.23.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655769/01c9e8f2-19b7-e8e3-d1db-bed469d92e85.png)

「current_userを指定しているけれど、そもそも誰もログインしていないよ！」と教えてくれています。

```
<% @items.each do |items| %>
 <% if current_user.favorited?(items) %>
        <%= link_to item_favorites_path(items), method: :delete %>
           
      <% else %>
         <%= link_to item_favorites_path(items), method: :post%>
      <% end %>
 <% end %>
 <% end %>
```

#####  if current_user.favorited?(items) 
・・userモデルで作成したメソッドを使用して、「delete（いいねを外す）」か「post（いいねをつける）」か、どちらの条件に当てはまるかという記述をしています。今回(items)はeach文のブロック変数を使用しているためこのような記述になります。
詳細ページなど単体のデータを取り出したい場合は（）内の変数を適切なものにすると表示されます！ 



```
<span class="favorite__count"><%= items.favorites.count %></span>
```
countをつけることで、お気に入りされた数が表示されます。



## まとめ

今回は以下のようなお気に入り機能の作り方をまとめました。


![スクリーンショット 2022-05-24 15.43.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655769/95e9db65-09b4-c636-e30a-3f589ba8a48b.png)

うまく表示されず苦戦しましたが、動きをつけることができてよかったです。
非同期でできると利便性がさらに上がると思いますので、
気になる方はぜひ調べてみてください！


こちらの記事がどなたかの参考になりましたら嬉しいです。