#フォロー機能
##準備
・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
https://qiita.com/MitsuguSueyoshi/items/e41e2ff37f143db81897