1人のユーザーは1つのプロフィールにいいねでき、
MVCの命名は「like」とします。
% rails g model like user_id:integer profile_id:integer
モデルとマイグレーションファイルを作成します。
テーブル
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テーブルを作成します。
モデル
class Like < ApplicationRecord
belongs_to :user
belongs_to :profile
end
has_many :likes, dependent: :destroy
has_many :likes, dependent: :destroy
アソシエーションを定義します。
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の機能の一部です。
Railsガイド
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の中はこんな感じ↓
要約:
current_userに紐付いたlikesテーブルの中で、profile_id:に現在いいねしようとしている
プロフィールのidが存在しているか?
ルーティング
resources :profiles do
resource :likes, only: [:create, :destroy]
end
ルーティングを設定します。
コントローラー
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に遷移させます。
エラーが発生したケースを想定しての記述です。
ビュー
<% 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 %>はいいねの数を表示しています。
<%= link_to profile_likes_path(@profile), method: :post do %>
<%= link_to profile_likes_path(@profile), method: :post, remote: true do %>
非同期にする場合は、link_toにremote: trueを付与します。