見出し画像

Rails フォロー機能 わかりやすく 解説

前提
☆必要なテーブル
・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のマイグレーションファイルを編集&実行
下記のように編集してください。

db/migrate/年月日時_create_relationships.rb
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モデルにアソシエーションを書いていきます!

app/models/relationship.rb
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モデルにアソシエーションを書いていくのですが、、、

ここが山場です。理解しにくい部分なのでしっかり解説します。

app/models/user.rb
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モデルにフォロー機能のメソッドを書いておきます。
これやった方が後々めちゃくちゃ楽です。

app/models/user.rb
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
下記のように書いていきます。

app/controllers/relationships_controller.rb
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
   @user = User.find(params[:relationship][:follow_id])
 end
end

⓺フォローボタン(form_for)をviewに設置

app/views/relationships/_follow_button.html.erbapp/views/relationships/_follow_button.html.erb

<% 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 %> がその例です。

⓻ルーティングを書く!終了!


config/routes.rb
Rails.application.routes.draw do
 resources :relationships, only: [:create, :destroy]
end


ありがとうございました!

この記事が気に入ったらサポートをしてみませんか?