【Rails】お気に入り登録とお気に入りした一覧を表示する巻

#はじめに

ネットショッピングしてていいなと思った商品見つけたらお気に入り登録したことありますよね？
そのとき、画面が更新されずにお気に入りやいいねすると♡マークの色が変わったりしますよね。あとは、お気に入りした商品を一覧で確認できたらいいですよね。今回はその実装のメモ書きです。

※出品や投稿機能(itemsテーブル)及びログイン機能(usersテーブル)が備わっていることを前提にお話を進めます。

#実装の手順（同期編）
完成のイメージはクリックした時に画面の一部が更新されて数字のカウントが増えていく・・・みたいなイメージ。ついでに削除機能も搭載します。お気に入りに追加したものは一覧で確認できるページも作成しちゃいます。


まずは、itemsテーブルとusersテーブルの中間テーブルとして
likesテーブルを作成。

↓追加するカラムとアソシエーションはこんな感じ↓
### itemsテーブル
|Column|Type|Options|
|------|----|-------|
＝＝＝略＝＝＝
※追加なし※
### Association
- has_many :likes
- has_many  :liked_users,  through:  : likes, source: :user #お気に入一覧作りたい時使うが今回は使わない


###likesテーブル    #中間テーブル
|Column|Type|Options|
|------|----|-------|
|item_id|integer|null: false|
|user_id|integer|null: false|

### Association
- belongs_to :item
- belongs_to :user


### usersテーブル
|Column|Type|Options|
|------|----|-------|
＝＝＝略＝＝＝
※追加なし※

### Association
- has_many :likes
- has_many  :liked_items,  through:  :likes, source: :item #お気に入一覧作りたい時使うが今回は使わない

これにてDB設計は完了
つづいてモデルの作成。以下のコマンドをターミナルに入力

```ターミナル
rails g model like
```
テーブルを作成するためマイグレーションファイルを編集

```2XXXXXXXXXXXX5_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer :item_id, null: false
      t.integer :user_id, null: false
    end
  end
end
```
からのrails db:migrateします。
そしたらモデルが生成されるのでアソシエーションを書いていきます。

```user.rb
has_many :likes #追加
```

```item.ruby
has_many :likes #追加
```

```like.rb
class Like < ApplicationRecord

  belongs_to :item
  belongs_to :user

  validates :user_id, presence: true
  validates :item_id, presence: true
  validates_uniqueness_of :item_id, scope: :user_id

  def self.like_method(item, user)
    Like.find_by(item_id: item.id, user_id: user.id)
  end

end
```

self.like_method(item, user)
ここでIF分の設定をしちゃいます。
likeテーブルにitem_idとuser_idに入ってるーーーって条件を設定します！
次にコントローラーを作成するため以下のコマンドを実行

```ターミナル
rails g controller likes
```
無事にコントローラーが作成されたら、
・ビューファイル
・コントローラー
・ルーティング
それぞれ設定していきます。

```routes.rb
ーー略ーー  
resources :items, only: [:new, :create, :edit, :update, :show, :destroy] do
    resources :likes, only: [:create, :destroy]
  end
ーー略ーー 
  resources :users, only: [:show] do
    resources :likes, only: [:index]
  end
ーー略ーー 
```
createとdestroyはitemsにネスト
indexはusersにネスト

```likes_controller.rb
class LikesController < ApplicationController

  before_action :set_item, only: [:create, :destroy]

  def index
    @items = Item.includes(:item_images).order("created_at DESC")
  end

  def create
    @like = Like.create(item_id: params[:item_id],user_id: current_user.id)
    redirect_to item_path(@item.id)
  end

  def destroy
    @like = Like.find_by(item_id: params[:item_id],user_id: current_user.id)
    @like.destroy
    redirect_to item_path(@item.id)
  end

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

end
```
createとdestroyはビューファイルがないのでリダイレクトの設定が必要になります。

```index.html.haml
ーー略ーー  
      - if user_signed_in? && Like.like_method(@item, current_user)
        .check_btn__like--add{data: {item_id: @item.id}}
          = link_to "/items/#{@item.id}/likes/:id" , method: :DELETE do
            = icon( "fa", "star")
            お気に入り
            = @item.likes.length
      - elsif user_signed_in?
        .check_btn__like{data: {item_id: @item.id}}
          = link_to "/items/#{@item.id}/likes", method: :POST do
            = icon( "fa", "star")
            お気に入り
            = @item.likes.length
      - else
        .check_btn__like
          = link_to new_user_session_path do
            = icon( "fa", "star")
            お気に入り
            = @item.likes.length
ーー略ーー  
```

お気に入り登録する時の画面はこちら

```index.html.haml
.mypage_Wrapper
  %section.mypage_title
    お気に入り一覧
  %section.like_Wrapper
    .like__box
      - @items.each do |item|
        - if user_signed_in? && Like.like_method(item, current_user)
          .like__box__product
            .like__image
              = link_to item_path(item.id) do
                = image_tag item.item_images[0].image.url
            .like__name
              = item.name
            %ul
              %li
                = item.price.to_s
              %li
                = "円"
              %li.like__tax
                = "(税込)"
              %li.like__icon
                = link_to "/items/#{item.id}/likes/:id" , method: :DELETE do
                  = icon("fas", "star")
.bottom_Wrapper
```
これが、お気に入りした商品一覧で表示するもの

```like.scss
.like_Wrapper {
  width: 100%;
}

.like__box {
  background-color:white;
  margin: 0 auto;
  width: 65%;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  &__product {
    width: 180px;
    height: 90%;
    margin: 25px 2% 0;
    transition: all .3s ease-in-out;
    .like__image {
      width: 180px;
      height: 140px;
      margin-bottom: 3%;
      img{
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }
    .like__name {
      font-weight: bold;
    }
    ul{
      display: flex;
      font-size: 16px;
      li{
        font-weight: bold;
      }
      .like__tax {
        font-size: 10px;
        writing-mode: lr-tb;
        width: 30px;
        line-height: 30px;
        margin-left: 2%;
        margin-right: 24%;
      }
      .like__icon {
        i {
          font-size:1.1em;
          color: orange;
        }
      }
    }
  }
  &__product:hover {
    cursor: pointer;
    transform: scale(1.1, 1.1);
  }
}
```
一応、CSSも
ここまでの実装で同期した状態で実装できるかと思います！
いったんここまで、Ajaxを利用した非同期通信を実装するとよりリッチになるのでそのことを書いて行けたらいいなと・・・
それはまた後日・・・

[![Image from Gyazo](https://i.gyazo.com/0a7cbee24a7dd7e01443d84893beb397.gif)](https://gyazo.com/0a7cbee24a7dd7e01443d84893beb397)

[![Image from Gyazo](https://i.gyazo.com/78335c082f44c945744d50bd922fdf07.gif)](https://gyazo.com/78335c082f44c945744d50bd922fdf07)

# 参考
https://techtechmedia.com/favorite-function-rails/#i
https://qiita.com/naberina/items/c6b5c8d7756cb882fb20
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9
https://qiita.com/shh-nkmr/items/48fe53282253d682ecb0