👻
[Rails]フォロー・フォロワー機能(through,中間テーブル,MVCの役割)
フォロー・フォロワーの考え方
上記図のように、
- フォローする側も、複数人フォローできる
- フォローされる側も、複数人からフォローされる
…ため、「多対多」の関係になる。
「多対多」を回避するために、中間テーブル(relationships)を作成する。
中間テーブル(relationships)は、誰が誰をフォローしているのかという情報を保持する。
つまり、
- フォローするユーザーのID(follower_id)
- フォローされるユーザーのID(followed_id)
を格納する。
followerテーブル・followedテーブルは、ユーザーテーブルに名前を付けるだけで、実際には作成しない。
この考え方を踏まえて、モデルを作成する!
モデル
rails g model relationship followed_id:integer follower_id:integer
relationship.rb
followingテーブル・followerテーブルは、ユーザーテーブルに名前を付けるだけで、実際には作成しない。⇒class_name: "User"
を付ける。
class Relationship < ApplicationRecord
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
end
1. through・中間テーブルについて
復習・前回もやったよ
through
については下記でもまとめた。(今見返すとあんまり理解できてなかったなあ)
has_many :through関連付けは、他方のモデルと「多対多」のリレーションシップを設定する場合によく使われます。この関連付けでは、2つのモデルの間に「第3のモデル」(joinモデル)が介在し、それを経由(through)して相手のモデルの「0個以上」のインスタンスとマッチします。
以下から引用。Railsガイドのthroughの説明箇所。
through
は、中間テーブルを介した向こう側のテーブルのデータを取ってくる。
下記の場合は、中間テーブルを通して…という意味になる。
user.rb
# フォローする側
# 中間テーブルへ
has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
# 中間テーブルを通して、フォローされる側テーブルへ
has_many :followings, through: :relationships, source: :followed
# フォローされる側
# 中間テーブルへ
has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
# 中間テーブルを通して、フォローする側テーブルへ
has_many :followers, through: :reverse_of_relationships, source: :follower
2. モデルに書くメソッド
ビジネスロジックはモデルで定義する。
ここでのロジックとは「このクラス・モデル(User)が何をできるのか?」という振る舞い。
- モデルはデータをどうするか定義する、ルールを書くというイメージ?
- コントローラは、データの受け渡しの処理。中間地点。
今回Userモデルに書いたメソッド。
- ユーザーの画像の表示、画像なければデフォルト画像を表示
- Userというデータに関する振る舞いなので、Userモデルに書く
- Userが持ってるデータをどう処理するかという振る舞い
- フォロー機能
- 自分(Userインスタンス=個々のユーザー)の状態が変わる処理なので、Userモデルの責任。(Fat Models=モデルになるべく責任を持たせる、モデルを太らせる)
- もしコントローラに書くと、コントローラは
relationships_controller
favorites_controller
task_comments_controller
など、機能ごとに増えていくので、Userが何をできるのかが分散してしまう(管理しづらくなる)。UserができることはUserモデルにまとめて書く。
- フォロー外す機能
- 同上。自分が他人をアンフォローする、という「Userの振る舞い」。
- フォローされてるかチェック
- Userが他Userに対してどんな関係を持っているかを見るので、Userに書くのが自然。
user.rb
def get_profile_image(width, height)
if profile_image.attached?
profile_image.variant(resize_to_limit: [width, height]).processed
else
'default_profile_image.png'
end
end
def follow(user)
relationships.create(followed_id: user.id)
end
def unfollow(user)
relationships.find_by(followed_id: user.id).destroy
end
def following?(user)
followings.include?(user)
end
復習になるリンクまとめ
前回もリンクさせていただいたところ。あやふやだったら読むこと!
前回似たようなこと自分でまとめた
前回参考にさせていただいた記事
ルーティング
resources :users, only: [:index,:show,:edit,:update] do
resource :relationships, only: [:create, :destroy]
get 'followings' => 'relationships#followings', as: 'followings'
get 'followers' => 'relationships#followers', as: 'followers'
end
コントローラー
rails g controller relationships
relationships_controller
class RelationshipsController < ApplicationController
def create
user = User.find(params[:user_id])
current_user.follow(user)
redirect_to request.referer
end
def destroy
user = User.find(params[:user_id])
current_user.unfollow(user)
redirect_to request.referer
end
def following
user = User.find(params[:user_id])
@users = User.followings
end
def followers
user = User.find(params[:user_id])
@users = user.followers
end
end
request.referer
とは、直前にアクセスしていたURL(リクエストの元のページ)を返す。
ビュー
<td>
フォロー数:<%= user.followings.count %>
<br>
フォロワー数:<%= user.followers.count %>
</td>
<td>
<% if current_user != user %>
<% if current_user.following?(user) %>
<%= link_to user_relationships_path(user.id), method: :delete do %>
フォローを外す
<% end %>
<% else %>
<%= link_to user_relationships_path(user.id), method: :post do %>
フォローする
<% end %>
<% end %>
<% end %>
</td>
参考文献
Discussion