#前提
☆必要なテーブル
・usersテーブル
・relationshipsテーブル(中間テーブルです)
☆ポイント
・アソシエーションが普通の「多対多」とは違う事(ちゃんと解説します!!)
・Userモデル、usersテーブルは作っている前提で説明します!!
#流れ
⓵relationshipsモデルを作る
⓶relationshipsのマイグレーションファイルを編集&実行
⓷userモデルとrelationshipsモデルにアソシエーションを書く
⓸userモデルにフォロー機能のメソッドを書く
⓹relationshipsコントローラを作成&編集
⓺フォローボタン(form_for)をviewに設置
⓻ルーティングを書く!終了!
#⓵relationshipsモデルを作る
今回はuserとtweetsの関係性とは違い、userテーブル同士で「多対多」の関係を作ります。何故ならフォロワーもまたuserだからです。イメージとしてはuserテーブル同士をrelationshipsという中間テーブルでアソシエーションを組むイメージです!
まずは、realtionshipsモデルを作っていきます。
$ 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 | integer | foreign_key: true |
follow_id | integer | foreign_key:{to_table: users} |
となります。
そもそも、relationshipsテーブルは中間テーブルなので、user_id
とfollow_id
は「t.references
」で作ってあげる必要があります。
そして、外部キーとしての設定をするためにオプションは「foreign_key: true
」とします。
でも!注意したいのがfollow_id
の参照先のテーブルはusersテーブルにしてあげたいので、{to_table: :users}
としてあげてます。
foreign_key: true
にすると存在しないfollowsテーブルを参照してしまうからです。
t.index [:user_id, :follow_id], unique: true
は、 user_id
と follow_id
のペアで重複するものが保存されないようにするデータベースの設定です!
これは、あるユーザがあるユーザをフォローしたとき、フォローを解除せずに、重複して何度もフォローできてしまうような事態を防いでいるだけです。
終わったら、マイグレーションファイルを実行してください!
$ rails db:migrate
#⓷relationshipsモデルとuserモデルにアソシエーションを書く
まずは、relationshipsモデルにアソシエーションを書いていきます!
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クラスという存在しないクラスを参照することを防ぎ、User クラスであることを明示しています。
要は「followモデルなんて存在しないので、userモデルにbelongs_toしてね!」って事です。
さらに、バリデーションも追加してどちらか一つでも無かった場合保存されないようにします!
次に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
end
1行目のhas_many :relationships
は大丈夫ですね。
2行目のhas_many :followings
とありますが、これはいまこのタイミングで命名したものです!followingクラス(モデル)を架空で作り出しました。
勿論、followingクラス(モデル)なんて存在しません。
なので、補足を付け足す必要があります。
through: :relationships
は「中間テーブルはrelationshipsだよ」って設定してあげてるだけです。
source: :follow
とありますが、これは
「relationshipsテーブルのfollow_id
を参考にして、followingsモデルにアクセスしてね」って事です。
結果として、user.followings と打つだけで、user が中間テーブル relationships を取得し、その1つ1つの relationship のfollow_id
から、「フォローしている User 達」を取得しています。
次にフォロワー(フォローされているuser達)をとってくるための記述をします。
結論から言うとフォローの逆をしてあげればいいのです。
3行目のhas_many :reverse_of_relationships
は
has_many :relaitonships
の「逆方向」って意味です。
これはこのタイミングで命名したものです。勿論reverse_of_relationshipsなんて中間テーブルは存在しません。なので、これも補足を付け足してやります。
class_name: 'Relationship'
で「relationsipモデルの事だよ〜」と設定してあげます。
次のforeign_key: 'follow_id'
ですが、これ何のこっちゃ分からないと思います。
これ、「relaitonshipsテーブルにアクセスする時、follow_id
を入口として来てね!」っていう事です。
ちょっと1行目のhas_many :relationships
を思い出してください。実はこれ
has_many :relationships, foreign_key: 'user_id'
って意味なんです!
要はこれもuser_id
を入り口にしてね、っていうだけです!
user_id
を入口として、relationshipsテーブルという家に「おじゃましま〜す」と入って、follow_id
という出口(=source: :follow)から出て、followingsテーブルからフォローしている人のデータをとってくるイメージです!
foregin_key = 入口
source = 出口
というのを念頭においてください。
これで100%理解出来るはずです。
4行目に行きます。has_many :followers
もこのタイミングで命名してます。勿論、followersなんてクラス存在しません。
through: :reverses_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
注意すべき点は、フォローが自分自身ではないか?とすでにフォローしていないか?の2点です!!!!
def follow
では、unless self == other_user
によって、フォローしようとしている other_user が自分自身ではないかを検証しています。self には user.follow(other)
を実行したとき user が代入されます。つまり、実行した User のインスタンスが self です!
更に、self.relationships.find_or_create_by(follow_id: other_user.id)
は、見つかれば Relation を返し、見つからなければ self.relationships.create(follow_id: other_user.id) としてフォロー関係を保存(create = new + save)することができます。これにより、既にフォローされている場合にフォローが重複して保存されることがなくなります!
def unfollow
では、フォローがあればアンフォローしています。また、relationship.destroy if relationship
は、relationship が存在すれば destroy します!if文はこのように書けます!
def following?
では、self.followings
によりフォローしている User 達を取得し、include?(other_user)
によって other_user が含まれていないかを確認しています!含まれている場合には、true を返し、含まれていない場合には、false を返します!
#⓹relationshipsコントローラを作成&編集
relationshipsコントローラ作ってください。
$ rails g controller relationships
下記のように書いていきます。
class RelationshipsController < ApplicationController
before_action :set_user
def create
following = current_user.follow(@user)
if following.save
flash[:success] = 'ユーザーをフォローしました'
redirect_to @user
else
flash.now[:alert] = 'ユーザーのフォローに失敗しました'
redirect_to @user
end
end
def destroy
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[: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 %>
※hamlで書くと下のような感じになります!
- 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|
= f.hidden_field :follow_id, value: user.id
= f.submit 'フォロー中', class: 'follow-now'
- else
= form_for(current_user.relationships.build) do |f|
= f.hidden_field :follow_id, value: user.id
= f.submit 'フォロー', class: 'follows'
あとは部分テンプレートでご自身の好きなところに置いちゃって下さい!!!
<%= render 'relationships/follow_button', user: @user %>
みたいな感じで!
#⓻ルーティングを書く!終了!
Rails.application.routes.draw do
resources :relationships, only: [:create, :destroy]
end
ありがとうございました!