Rails フォロー機能まとめ


##記事作成理由

１：プログラミング初学者の私が見返すため。
２：私のような初学者の学習に少しでも役立てればと思ったため。

※誤った記述や知識があればお知らせいただけると幸いです。

<br>

##開発環境

・ruby: 2.6.3
・rails: 5.2.4.5
・OS: macOS Catalina ver10.15.7
・Cloud9

<br>

##前提条件

・Userモデル,usersテーブルがある

・Bootstrap導入済み
・deviseを使ってログイン機能実装済み

・CRUD機能の基礎について学習済み

<br>

##実装の流れ

１：Relationshipモデルの作成
２：中間テーブル(relationshipsテーブル)の作成
３：アソシエーションの設定
４：アクションの定義
５：Viewファイルに記述

<br>
###１：Relationshipモデルの作成

```ターミナル.
rails g model Relationship
```
<br>
###２：中間テーブル(relationshipsテーブル)の作成

マイグレーションファイルにカラムを追加

```db/migrate/○○○○_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.2]
  def change
    create_table :relationships do |t|

      t.references :follower, foreign_key: {to_table: :users}
      t.references :followed, foreign_key: {to_table: :users}

      t.timestamps

      t.index [:follower_id, :followed_id], unique: true
    end
  end
end
```

```ターミナル.
rails db:migrate
```
マイグレーションファイルの反映忘れずに！
<br>
####ーー解説ーー

relationshipsテーブルの中身はこのようになります。

| カラム | タイプ| オプション |
|:-:|:-:|:-:|
|follower_id   |integer   |foreign_key: {to_table: :users}   |
|followed_id |integer   |foreign_key: {to_table: :users}   |

relationshipsテーブルは中間テーブルであるため、
followerカラムとfollowedカラムは`t.references`をつけて生成します。
<br>
__(ポイント！)__
今回生成したカラム (follower_id と followed_id) は自由に命名することができます。
他記事ではfollowing_idやfollows_idなど様々な命名がされています。自分が１番理解しやすいように命名しましょう。
(色々な記事を調べて、様々な命名を見ると頭が混乱します。僕だけかもしれませんが・・・)
<br>
また、follower、followedどちらも外部キーとして設定します。 `foregin_key（外部キー)`
ここで`foregin_key :true`としてしまうと、followersテーブルやfollowedsテーブルなど存在しないテーブルを参照してしまいエラーになります。
それを避けるために`{to_table: :users}`として、usersテーブルのidを参照するよう紐づかせています。
<br>
`t.index [:follower_id, :followed_id], unique: true`
これにより、２つのカラムの中身が同じ組み合わせでデータを保存することを防ぎます。
＝同じユーザーを２回フォローできないようにしています。
<br>

中間テーブル概念についてはこちら２つの記事がとても参考になりました。
[【Railsでフォロー機能を作ろう】]
(https://hajimeteblog.com/rails-follow/)
[【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル](https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1)

<br>
###３：アソシエーションの設定

```relationship.rb

class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"

  validates :follower_id, presence: true
  validates :followed_id, presence: true
end
```
<br>
####ーー解説ーー

■2,3行目
relationshipsテーブルでforeign_keyとしてfollower_id , followed_id を設定しているため、モデル名も`Follower`, `Followed`とします。

しかし、FollowerモデルもFollowedモデルも実在しない為、`class_name: "User"`とオプションを追記し、関連するモデル(__本当のモデル名__)を指定します。
__※__このとき、"User"は文字列扱いになるため、ダブルクォーテーションで囲んでいます。

■4,5行目
validatesの説明は割愛します。
(わからない方は「バリデーション」)でお調べください。
<br>

```user.rb
class User < ApplicationRecord

  # フォローするユーザーから見た中間テーブル
  has_many :active_relationships,
                      class_name: "Relationship",
                      foreign_key: "follower_id",
                      dependent: :destroy

  # フォローされているユーザーから見た中間テーブル
  has_many :passive_relationships,
                      class_name: "Relationship",
                      foreign_key: "followed_id",
                      dependent: :destroy

  # 中間テーブルactive_relationshipsを通って、フォローされる側(followed)を集める処理をfollowingsと命名
  # フォローしているユーザーの情報がわかるようになる
  has_many :followings, through: :active_relationships, source: :followed

  # 中間テーブルpassive_relationshipsを通って、フォローする側(follower)を集める処理をfollowingsと命名
  #　フォローされているユーザーの情報がわかるようになる
  has_many :followers, through: :passive_relationships, source: :follower

end
```
<br>
####ーー解説ーー

ここが鬼門です。がんばっていきましょう。
4つのhas_manyがありますが、上から２つずつに分けて説明します。

まず中間テーブルの見方を2つに分けます。
１：フォロー__する__ユーザーから見た視点
２：フォロー__される__ユーザーから見た視点

本来であれば has_many :relationships とすればいいのですが、
見方が２つあるため、２通りの定義をしなければなりません。

そのため、ここでは
フォロー__する側__を `active_relationships` と命名し、(__２__行目)
フォロー__される側__を `passive_relationships` と命名しています。(__７__行目)
↓
※先ほど`user.rb`で作成したモデル(Follower,Followed)と同様、これらも好きに命名することができます。
他記事を見て比較する時など、命名が異なるケースがあるため、注意しましょう。<br>
そして、参照元のモデルはどちらも`Relationshipモデル`であるため、
`class_name: "Relationship"`とオプションを追加します。

しかし、このままではRelationshipsテーブルのどちら(follower , followed)を参照すればいいのかがわかりません。
そこで、下記のように指定してあげます
`active_relationships`は、`foreign_key: "follower_id"`を参照
`passive_relationships`は、`foreign_key: "followed_id"`を参照
<br>
`dependent: :destroy`は、とあるUserが削除された時にそれに紐づくrelationshipのデータも削除されるようにしています。
<br>

次に、こちらの文ですが、
`has_many :followings, through: :active_relationships, source: :followed`
`has_many :followers, through: :passive_relationships, source: :follower`

これらはコードのコメントに記載の通り、定義したテーブルを通って(through)、sourceで指定したモデルの参照元からデータを集めることができます。

これにより
`@user.followings`　(userがフォローしている人の情報)
や
`@user.followers`(userがフォローされている人の情報＝userをフォローしている人の情報)
といった表記が可能になります。

※くどいようですが、、`followings`や`followers`も好きに命名することができます。
他記事を見て比較する時など、命名が異なるケースがあるため、注意しましょう。<br>

これでアソシエーションは__完成__です。
<br>

###４：アクション定義

user.rb と relationships.controller.rbにアクションを定義します。

```user.rb
#下記３つのアクションを追記
class User < ApplicationRecord		
  
  # フォローする
  def follow(user_id)
    active_relationships.create(followed_id: user_id)
  end
			
  # フォローを外す
  def unfollow(user_id)
    active_relationships.find_by(followed_id: user_id).destroy
  end
			
  # すでにフォローしているのか確認
  def following?(user)
    followings.include?(user)
  end
end
```

```relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :authenticate_user!

  def create
    current_user.follow(params[:user_id])
    redirect_to request.referer
  end

  def destroy
    current_user.unfollow(params[:user_id])
    redirect_to request.referer
  end

  # フォローしている人一覧
  def follower
    user = User.find(params[:user_id])
    @users = user.followings
  end

  # フォローされている人一覧
  def followed
    user = User.find(params[:user_id])
    @users = user.followers
  end

end
```

<br>

###５：Viewファイルに記述

ここからはあくまで例になります。
定義している変数などによって記述の仕方は変わります。
※一部抜粋<br>
ーー__フォロー数、フォロワー数表示、フォローボタン__ーー

```html:users/index.html.erb
<tbody>
  <% @users.each do |user| %>
  <tr>
    <td><%= attachment_image_tag user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg", size:"40x40" %></td>
    <td><%= user.name %></td>
    <td><%= "フォロー数: #{user.active_relationships.count}" %></td>
    <td><%= "フォロワー数: #{user.passive_relationships.count}" %></td>
    <td>
      <% if current_user.id != user.id %>
        <% if current_user.following?(user) %>
        <%= link_to 'フォローを外す', user_relationships_path(user.id), method: :DELETE %>
        <% else %>
        <%= link_to 'フォローする', user_relationships_path(user.id), method: :POST %>
        <% end %>
      <% end %>
    </td>
    <td><%= link_to "Show", user_path(user.id) %></td>
  </tr>
  <% end %>
</tbody>
```

<br>
ーー__フォローしている人一覧__ーー

```html:relationships/follower.html.erb
<tbody>
  <% @users.each do |user| %>
    <tr>
      <td><%= attachment_image_tag user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg" , size:"40x40" %></td>
      <td><%= user.name %></td>
      <td>フォロー数：<%= user.followings.count %></td>
      <td>フォロワー数：<%= user.followers.count %></td>
      <td><%= link_to "show", user_path(user.id) %> </td>
    </tr>
  <% end %>
</tbody>
```

<br>
ーー__フォローされている人一覧__ーー

```html:relationships/followed.html.erb
<tbody>
  <% @users.each do |user| %>
    <tr>
      <td><%= attachment_image_tag user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg" , size:"40x40" %></td>
      <td><%= user.name %></td>
      <td>フォロー数：<%= user.followings.count %></td>
      <td>フォロワー数：<%= user.followers.count %></td>
      <td><%= link_to "show", user_path(user.id) %> </td>
    </tr>
  <% end %>
</tbody>
```
<br>
以上です。
閲覧ありがとうございました！

誤字、脱字、誤り等ございましたら教えていただけると幸いです。