[Rails] いいねをつける

１人のユーザーは１つのプロフィールにいいねでき、
MVCの命名は「like」とします。
[![Image from Gyazo](https://i.gyazo.com/7ab364ff2b0dcfe8f8d367cd91bf05be.png)](https://gyazo.com/7ab364ff2b0dcfe8f8d367cd91bf05be)
---

% rails g model like user_id:integer profile_id:integer
モデルとマイグレーションファイルを作成します。

テーブル
---

```db/migrate/〜_create_likes.rb
class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.integer :user_id
      t.integer :profile_id

      t.timestamps
    end
  end
end
```
% rails db:migrate
likesテーブルを作成します。

モデル
---

```models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :profile
end
```

```models/profile.rb
has_many :likes, dependent: :destroy
```


```models/user.rb
has_many :likes, dependent: :destroy
```
アソシエーションを定義します。

---

```models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :profile

  validates_uniqueness_of :profile_id, scope: :user_id
end
```
バリデーションを設定します。
profile_idとuser_idの組み合わせが一意性という制限です。

scopeとは、modelで使えるActive Recordの機能の一部です。
<a href="https://railsguides.jp/active_record_querying.html#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97">Railsガイド</a>

---

```models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :profile
  
  validates_uniqueness_of :profile_id, scope: :user_id

  def already_liked?(profile)
    self.likes.exists?(profile_id: profile.id)
  end
end
```
ユーザーが既にいいねしているかを確認するメソッドを作成します。
理由は、このあとviewsで条件分岐に使うからです。

exists?
該当の値があればtrue、なければfalseを返すメソッドです。

selfにはcurrent_userのデータが入ります。
データが入ってない現在のselfの中はこんな感じ↓
[![Image from Gyazo](https://i.gyazo.com/cc31236198f7c568f56f9199b5bdb83b.png)](https://gyazo.com/cc31236198f7c568f56f9199b5bdb83b)

要約：
current_userに紐付いたlikesテーブルの中で、profile_id:に現在いいねしようとしている
プロフィールのidが存在しているか？


ルーティング
---

```config/routes.rb
resources :profiles do
    resource :likes, only: [:create, :destroy]
end
```
ルーティングを設定します。

コントローラー
---

```likes_controller.rb
class LikesController < ApplicationController
  def create
    @like = current_user.likes.create(profile_id: params[:profile_id])
    redirect_back(fallback_location: root_path)
  end

  def destroy
    @profile = Profile.find(params[:profile_id])
    @like = current_user.likes.find_by(profile_id: @profile.id)
    @like.destroy
    redirect_back(fallback_location: root_path)
  end
end
```

redirect_back
直前のページにリダイレクトするメソッドです。
index, showなど、複数の画面でいいねしたいのでこう書いています。
もしshowでしかいいねしないといった場合はredirect_toで大丈夫です。

fallback_location: url
もし直前のページが見つからなかった場合に、指定のURLに遷移させます。
エラーが発生したケースを想定しての記述です。


ビュー
---

```views/show.html.erb
　<% if user_signed_in? && current_user != @profile.user %>
      <% if current_user.already_liked?(@profile) %>
        <%= link_to profile_likes_path(@profile), method: :delete do %>
          <svg xmlns="http://www.w3.org/2000/svg" class= "unlike" width="16" height="16" fill="currentColor" class="bi bi-heart-fill" viewBox="0 0 16 16">
            <path fill-rule="evenodd" d="M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314z"/>
          </svg>
        <% end %>
      <% else %>
        <%= link_to profile_likes_path(@profile), method: :post do %>
          <svg xmlns="http://www.w3.org/2000/svg" class= "like" width="16" height="16" fill="currentColor" class="bi bi-heart" viewBox="0 0 16 16">
            <path d="M8 2.748l-.717-.737C5.6.281 2.514.878 1.4 3.053c-.523 1.023-.641 2.5.314 4.385.92 1.815 2.834 3.989 6.286 6.357 3.452-2.368 5.365-4.542 6.286-6.357.955-1.886.838-3.362.314-4.385C13.486.878 10.4.28 8.717 2.01L8 2.748zM8 15C-7.333 4.868 3.279-3.04 7.824 1.143c.06.055.119.112.176.171a3.12 3.12 0 0 1 .176-.17C12.72-3.042 23.333 4.867 8 15z"/>
          </svg>
        <% end %>
      <% end %>
      <%= @profile.likes.count %>
　 <% end %>
```
ビューを作成します。
少し前に作成したalready_liked?メソッドを使って、
既にいいねしてあれば削除、まだいいねしてなければ保存するという条件分岐をします。

svgはアイコンの表示です。
<%= @profile.likes.count %>はいいねの数を表示しています。

---

```views/show.html.erb
<%= link_to profile_likes_path(@profile), method: :post do %>

<%= link_to profile_likes_path(@profile), method: :post, remote: true do %>
```

非同期にする場合は、link_toにremote: trueを付与します。