この記事では、RailsでTwitterやInstagramのようなユーザーフォロー機能を実装する際のポイントを紹介します。
1. バージョン情報 macOS:12.6 Ruby:3.1.2 Rails:6.1.6 2. 実装時のポイント 2-1. モデルの関連 Friendshipモデルの作成 $ rails g model Friendship follower_id:integer followed_id:integer --no-fixture --no-test-framework follower_id:フォロー(という行為)をしているユーザーのid 自分にとってのフォロワーじゃなくてフォローをしている人という意味のフォロワー(⇄フォロイー) followed_id:フォローされてるユーザーのid 意味的にはfollowee_idでもいい 生成されたdb/migrate/XXXX_create_friendships.rbに以下を追記。
add_index :friendships, :followed_id add_index :friendships, [:follower_id, :followed_id], unique: true add_index :friendships, [:follower_id, :followed_id], unique: trueが:follower_idで始まっているため、add_index :friendships, :follower_idは定義不要 unique: trueでデータベース側からユニーク制約をつけ、あるユーザーが同じユーザーを2回以上フォローすることを防ぐ(モデル側のバリデーションによる一意性チェックは後述) $ rails db:migrate User/Friendshipの関連付け app/models/friendship.rb
class Friendship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" validates :follower_id, uniqueness: { scope: :followed_id } end validates :follower_id, uniqueness: { scope: :followed_id } モデル側のバリデーションによる一意性チェック scopeを指定することでfollower_idとfollowed_idの組み合わせの一意性を保つ(あるユーザーが同じユーザーを2回以上フォローすることを防ぐ) app/models/user.rb
class User < ApplicationRecord has_many :active_friendships, class_name: 'Friendship', foreign_key: :follower_id, dependent: :destroy, inverse_of: :follower has_many :passive_friendships, class_name: 'Friendship', foreign_key: :followed_id, dependent: :destroy, inverse_of: :followed end class_name class_nameオプションはhas_manyで指定した関連付け名と実際のモデル名が違う場合に使用するもの。今回のように一つのFriendshipモデルに対して二つの関連(active_friendshipsとpassive_friendships)を持たせたい場合、has_many :friendships(でuser.friendships)だとどちらの関連か区別できない。なのでhas_many :active_friendships(でuser.active_friendships)とする。ただこのままだとRailsは存在しないactive_friendshipsテーブルを探しに行くことになるので、実際に存在するfriendshipsテーブルを使うにはhas_many :active_friendships, class_name: 'Friendship'とする。以上によりactive_friendshipsという関連を実際のモデル名のFriendshipと対応付けることができる。 Railsガイド: Active Record の関連付け - 4.1.2.2 :class_name foreign_key 今回の場合、Railsは自動的にfriendshipsテーブルのuser_idを探しに行くが、friendshipsテーブルにuser_idカラムはないので、foreign_keyオプションで明示的にfollower_id(またはfollowed_id)を指定する必要がある Railsガイド: Active Record の関連付け - 4.1.2.5 :foreign_key dependent: :destroy Userモデルのデータリソースが削除されるとそれに紐づくFriendshipモデルのデータリソースも同時に削除される Railsガイド: Active Record の関連付け - 4.1.2.4 :dependent inverse_of Railsガイド: Active Record の関連付け - 3.5 双方向関連付け # inverse_ofの挙動 irb> a = User.first irb> b = a.active_friendships.first irb> a.name == b.follower.name => true irb> a.name = 'David' irb> a.name == b.follower.name => true # inverse_ofを指定しないとfalse # user(自分)が相手をフォローした場合 user.active_friendships.first.follower #=> 自分 user.active_friendships.first.followed #=> 相手 user.passive_friendships.first.follower #=> 相手 user.passive_friendships.first.followed #=> 自分 2-2. フォロー数・フォロワー数の表示 app/models/user.rb
...