フォロー機能
準備
・userテーブル
・relationshipsテーブル(usersテーブルとusersテーブルの間に入るテーブル)
relationshipsのマイグレーションファイルを編集&実行
$ rails g model Relationship
relationshipsモデルを作成し、マイグレーションファイルを編集&実行します。
class CreateRelationships < ActiveRecord::Migration[5.0]
def change
create_table :relationships do |t|
t.references :user, foreign_key: true
t.references :follow, foreign_key: { to_table: :users }
t.timestamps
t.index [:user_id, :follow_id], unique: true
end
end
end

上記がrelationshipsテーブルのカラムになります。
user_idテーブルとfollow_idテーブルを関連づけるため、t.referencesと記載する必要があります。
外部キーを設定するためにforeign_key: trueを追記する。
followキーの参照テーブルとしては{ to_table: :users }を追記します。
※foreign_key: tureだと無いはずのフォローテーブルを参照してしまいます。
t.index [:user_id, :follow_id], unique: trueはユーザIDとフォローIDのペアで重複して保存されるのを防いでいる。
rails db:migrateでマイグレーションファイルを実行する。
relationshipsモデルとuserモデルのアソシエーションを書く
class Relationship < ApplicationRecord
belongs_to :user
belongs_to :follow, class_name: 'User'
validates :user_id, presence: true
validates :follow_id, presence: true
end
class_name: 'User'と補足設定することで、Followクラスという本来無いクラスへの参照を防いでいます。
要は「followモデルは存在しないため、userモデルにbelongs_toをする」ということになります。
class User < ApplicationRecord
has_many :relationships
has_many :followings, through: :relationships, source: :follow
has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
has_many :followers, through: :reverse_of_relationships, source: :user
end
has_many :followings, through: :relationships, source: :follow
has_many :followingsこの部分はfollowingクラス(モデル)を架空で作成しています。
そのあとにthrough :relationshipsと記載しrelationshipsテーブルを中間に設定しています。
source: :followは「relationshipsテーブルのfollow_IDを参考にしてfollowingsモデルにアクセス」を指定しています。
なので、user.followingsと打つだけでuserが中間テーブルrelationshipsを取得し、1つ1つのrelationshipsのfollow_idから「フォローしているユーザ達」を取得しています。
次にフォロワー(フォローされているuser達)を取って来るための記述
3行目のhas_many :reverse_of_relationshipsはhas_many :relationshipsの「逆方向」の意味になります。
user_idを入り口にfollow_idという出口から出てfollowingsテーブルからフォローしてくれる人のデータを取ってくる。
foregin_key = 入口
source = 出口
この概念を持っておくと理解しやくなります。
has_many :followersと命名していますがfollowersクラスはありません。
through: :reverse_of_relationshipで「中間テーブルはreverses_of_relationshipにしてね」と設定し、source: :userで「出口はuser_id、userテーブルから自分をフォローしているuserをとってくる」と設定している。
userモデルにフォロー機能のメソッドを記載する
userモデルにフォロー機能メソッドを記載する。
class User < ApplicationRecord
has_many :relationships
has_many :followings, through: :relationships, source: :follow
has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
has_many :followers, through: :reverse_of_relationships, source: :user
def follow(other_user)
unless self == other_user
self.relationships.find_or_create_by(follow_id: other_user.id)
end
end
def unfollow(other_user)
relationship = self.relationships.find_by(follow_id: other_user.id)
relationship.destroy if relationship
end
def following?(other_user)
self.followings.include?(other_user)
end
end
def follow(other_user)
unless self == other_user
self.relationships.find_or_create_by(follow_id: other_user.id)
end
end
unless self == other_userによってフォローしようとしているother_userが自分自身では無いかを検証している。selfにはuser.follow(other)を実行したときuserが代入されます。つまり実行したUserのインスタンスがselfになります。
def following?ではself.followingsによりフォローしているUser達を取得し、include?(other_user)によってother_userが含まれていないかを確認している。含まれている場合はtrue、含まれていなければfalseを返す。
def followではunless self == other_userによってフォローしようとしている。ohter_userが自分自身では無いかを検証しています。
selfにはuser.follow(other)を実行したときuserが代入されます。実行したUserのインスタンスがselfになります。
self.relationships.find_or_create_by(follow_id: other_user.id)は見つかればrelationを返し、見つからなければself.relationships.find_or_create_by(follow_id: other_user.id)としてフォロー関係をself.relationships.create(follow_id: other_user.id) としてフォロー関係を保存することができる。これによってフォローされている場合にフォローが重複して保存されることがなくなります。
def unfollowingではフォローがあればアンフォローしてくれます。またrelationship.destroy if relationshipはrelationshipが存在すればdestroyする。
def following?ではself.followingsによりフォローしているUser達を取得し、include?(other_user)によってother_userが含まれていないかを確認している。
relationshipsコントローラを作成&編集
$ rails g controller relationships
まずはrelationshipsコントローラを作成しています。
class RelationshipsController < ApplicationController
before_action :set_user
def create
user = User.find(params[:relationship][:follow_id])
following = current_user.follow(user)
if following.save
flash[:success] = 'ユーザーをフォローしました'
redirect_to user
else
flash.now[:alert] = 'ユーザーのフォローに失敗しました'
redirect_to user
end
end
def destroy
user = User.find(params[:relationship][:follow_id])
following = current_user.unfollow(user)
if following.destroy
flash[:success] = 'ユーザーのフォローを解除しました'
redirect_to user
else
flash.now[:alert] = 'ユーザーのフォロー解除に失敗しました'
redirect_to user
end
end
private
def set_user
user = User.find(params[:relationship][:follow_id])
end
end
コントローラに上記を記載します。
フォローボタン(form_for)をviewに設置
<% unless current_user == user %>
<% if current_user.following?(user) %>
<%= form_for(current_user.relationships.find_by(follow_id: user.id), html: { method: :delete }) do |f| %>
<%= hidden_field_tag :follow_id, user.id %>
<%= f.submit 'Unfollow', class: 'btn btn-danger btn-block' %>
<% end %>
<% else %>
<%= form_for(current_user.relationships.build) do |f| %>
<%= hidden_field_tag :follow_id, user.id %>
<%= f.submit 'Follow', class: 'btn btn-primary btn-block' %>
<% end %>
<% end %>
<% end %>
'''
Rails.application.routes.draw do
resources :relationships, only: [:create, :destroy]
end
'''
あとはルーティングを記載して完了です。
参考URL