【Rails】お気に入り機能

# 実装したい機能
- 投稿表示ページに、お気に入りボタン設置
- 記事投稿者でない、ログイン中のユーザーのみお気に入り登録できる。
- 記事投稿者、未ログインは、お気に入り登録できない。
- お気に入りボタンをクリックしたら、Ajax通信でデータ保存。
- ユーザーのマイページに、お気に入りリスト一覧を表示。

# 実装の手順
1. お気に入りテーブル作成（ユーザー/投稿テーブルの中間テーブルに相当）
1. ルーティング： 記事投稿のルーティングにネストの形で記述。
1. お気に入りコントローラー: create、destroyアクションを定義。
1. Userコントローラー: ユーザーのマイページで呼び出す
1. ビュー: 投稿表示ページに、お気に入りボタン設置。マイページに、お気に入り一覧表示。



## 1. お気に入りテーブル作成
- 誰が、どの記事に、お気に入りしたかを管理したいので、favoriteモデル（user_id、post_idカラム）を生成。

```rb:ターミナル
% rails g model Favorite user:references post:references  # Favoriteモデル作成
% rails db:migrate        # 下記のマイグレーション実行
```

```rb:マイグレーションファイル
class CreateFavorites < ActiveRecord::Migration[5.0]
  def change
    create_table :favorites do |t|
      t.references :user, foreign_key: true, null: false
      t.references :post, foreign_key: true, null: false
      t.timestamps
    end
  end
end
```
アソシエーションを組む。
ユーザー、記事が削除されたら、お気に入りも削除（dependent: :destroy オプション）。

```rb:user.rb
has_many :favorites, dependent: :destroy     # ユーザー/お気に入り → 1:多
```
```rb:post.rb
belongs_to :user, optional: true
has_many :favorites, dependent: :destroy     # 記事/お気に入り → 1:多
```
```rb:favorite.rb
belongs_to :user   # ユーザー/お気に入り → 1:多
belongs_to :post   # 記事/お気に入り    → 1:多

validates_uniqueness_of :post_id, scope: :user_id    # バリデーション（ユーザーと記事の組み合わせは一意）
# 同じ投稿を複数回お気に入り登録させないため。
```


## 2. ルーティング
- 記事詳細表示のルーティングにネスト（createとdestory）。
- マイページのルーティングにネスト。

```rb:routes.rb
# マイページのルーティングにネスト
  resources :users, only: [:show, :edit, :update] do
    get :favorites, on: :collection
  end

# 記事詳細表示のルーティングにネスト
  resources :posts, expect: [:index] do
    resource :favorites, only: [:create, :destroy]
  end

```








## 3、 4. コントローラーの定義
- 誰が？： ログイン中のユーザー(current_user)に限定。投稿者本人は除外。
- どの投稿に？: 自分が投稿していない投稿に対して。 
- お気に入りコントローラー: create、destroyアクションを定義。
- Userコントローラー: ユーザーのマイページで呼び出す

```rb:favorites_controller
  before_action :set_post
  before_action :authenticate_user!   # ログイン中のユーザーのみに許可（未ログインなら、ログイン画面へ移動）

  # お気に入り登録
  def create
    if @post.user_id != current_user.id   # 投稿者本人以外に限定
      @favorite = Favorite.create(user_id: current_user.id, post_id: @post.id)
    end
  end
  # お気に入り削除
  def destroy
    @favorite = Favorite.find_by(user_id: current_user.id, post_id: @post.id)
    @favorite.destroy
  end

  private
  def set_post
    @post = Post.find(params[:post_id])
  end
```
```rb:user_controller
  def show
    @user = User.find(params[:id])
    @posts = @user.posts

    favorites = Favorite.where(user_id: current_user.id).pluck(:post_id)  # ログイン中のユーザーのお気に入りのpost_idカラムを取得
    @favorite_list = Post.find(favorites)     # postsテーブルから、お気に入り登録済みのレコードを取得
  end
```





## 5. ビュー
- 投稿表示ページに、お気に入りボタン設置。
- お気に入り登録/削除でAjax通信(remote: true で実装)。
- Ajax通信で、お気に入り登録数の表示を更新。登録済みか？でボタンcssも変更させる。

```haml:favorites/_favorite.html.haml
#favorite{ id: @post.id }
  - if !Favorite.exists?(user: current_user, post_id: @post.id)   # お気に入り未登録の時
    = link_to post_favorites_path(@post), method: :post, remote: true, class: "favorite-btn favorite-post" do
      .i.far.fa-thumbs-up
        %span いいね！
        = @post.favorites.length   # お気に入り登録数
  - else    # お気に入り登録済みの時
    = link_to post_favorites_path(@post), method: :delete, remote: true, class: "favorite-btn favorite-delete" do
      .i.far.fa-thumbs-up
        %span いいね！
        = @post.favorites.length   # お気に入り登録数
```
- **remote: true** : link_to や form_for/form_tag、button_to などに使えるオプション。  
**・** データは、コントローラーへ送信し、Ajax通信してくれる。ページ移動ではなく、JSを探してくれる(ページ移動ストップ)。  
**・** お気に入りボタンクリックで、発火させるJSの動作を_favorite.html.hamlと同じ階層に作成(Ajax通信で呼び出す)。

```haml:favorites/create.erb
$("#favorite_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
  <!-- ｊ メソッドで、JSとして認識してくれて、非同期で更新してくれる -->
```
```haml:favorites/destroy.erb
$("#favorite_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
```

- マイページに、お気に入り一覧表示。

```haml:users/show.html.haml
- if @favorite_list.present?
  - @favorite_list.each do |post|
    = post.title
    = post.content
```