😊

[Rails]follow機能作成- association N:Nについて

2023/01/30に公開
6

RailsでこのようなFollow/follwer機能を作るにあたって必要な、
アソシエーションの復習と、N:Nについてからやっていきます!

ここで理解が必要な用語まとめ

これは簡単になので、以下で詳しくやっていきます!

用語 意味 補足
リレーションシップ(relationship) 繋がり ここでは、テーブル同士の繋がり(関連)のこと
主キー(PK:Praimary Key) primary:最初の テーブルの情報を一意に識別するためのもの
外部キー(foreign key) foreign: 外国にある 関連したテーブル間を結ぶために設定する列のこと
アソシエーション(association) 繋がり、関連性 異なる 2 つのモデルの間に、1:N の関連性を持たせるもの

association(アソシエーション)

異なる 2 つのモデルの間に、1:N の関連性を持たせるもの。
もっとわかりやすく言うと、テーブル同士のリレーションシップを作り、
それをモデル上の関係として操作できるようにする仕組み

主キー(primary key)

テーブルの情報を一意に識別するためのもの

外部キー(foreign key)

関連したテーブル間を結ぶために設定する列のこと。
データの整合性をデータベースに保証させるために利用。
(belongs_toで参照する場合に必ず必要。)

別の言い方すると…
主キー(primary key)を参照するためのカラムのこと.
もっとわかりやすくいえば、他のテーブルのデータを参照する事が出来るように制約を付けたカラムのこと

associationを記述しよう

上記のイラストのように、アソシエーションは二種類ある!
どっちの視点から見るかで変わるよ!
(=> このどちらから、がよく説明で書かれている、[ 1:Nの関係 ]のことだよ!)

1. has_manyメソッド

他のモデルを複数持っている事を定義する

上記のイラストでいくと、
userモデルからarticleモデルへアソシエーションを結ぶ
User(1) has_many Articles(N)

2. belongs_toメソッド

他のモデルに属している事を定義する.

上記のイラストでいくと
articleモデルからUserモデルへのアソシエーションを結ぶ
article(N) belong_to user(1)**

Following機能の作成方法

今回はこのような感じのものを作成していきます。

<大まかな作成手順>

  1. relationshipsモデルを作る
  2. relationshipsのマイグレーションファイル作成
  3. userモデルとrelationshipsモデルをアソシエーションする
  4. userモデルにフォロー機能のメソッドを書く
  5. relationshipsコントローラを作成&編集
  6. viewの編集 (ボタン設置等)
  7. ルーティングの記述

Following機能は、N:Nだから中間モデルが必要

"Followするひと" と "followされる人"は、どちらも、 "User"であって、多:多なのだ。

多:多の関係性になった場合は、中間テーブルを作成して、1:多にする必要がある。
二つのテーブルの間に、双方のidをForeign_key としてもつ中間テーブルを作成。

<中間テーブルの特徴>

  • 接続している2つのテーブルのForeign_keyを持っている。
  • null(空)を出すことなく、レコードを追加することができる。

1.中間テーブル作成  (model/tableの作成)

  • 今回の中間テーブルでのモデル名は "Relationship"とする
    $ rails g model Relationship

そして今回のテーブルは、このようにする。

カラム名
id integer 主キー (PK)
follower_id(class_name:"User") integer フォローするユーザのid
followed_id(class_name:"User") integer フォローされるユーザのid

Followする人、Followされる人ともに "User"なので、
ここでテーブルを作成するにあたり、それぞれにUserモデルに属する、という記述が必要。

<Relationshipモデルmigrationファイル>

t.integer:follower_id, class_name:"User"
t.integer:followed_id, class_name:"User"

class_name オプションについて

モデル名を直接指定するもの。
関連名と参照先のクラス名を異なるものに置き換えることができるオプション
(関連先のモデルを参照する際の、名前の変更ができるもの.)
このように、多:多になった場合などで、中間テーブルを作成する際など
関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合に役立つ.

<記述型>

class_name:"関連先モデル名"
  • 記述できたらmigrationする  $ rails db:migrate

2. associationしていく

Userモデルと、Relationshipモデルの繋がりを記述していく。

まずはrelationshipモデルの記述

<relationship.rb>

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

Userモデルにも記述

ここは少し多いので下で解説します。
< user.rb >

class User < ApplicationRecord
.
.
has_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy # ① フォローしている人の取得
has_many :followed, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy # ② フォローされているの人取得

has_many :following_user, through: :follower, source: :followed # 自分がフォローしている人
has_many :follower_user, through: :followed, source: :follower # 自分をフォローしている人


  # ユーザーをフォローする
  def follow(user_id)
    follower.create(followed_id: user_id)
  end

  # ユーザーのフォローを外す
  def unfollow(user_id)
    follower.find_by(followed_id: user_id).destroy
  end

  # フォローしていればtrueを返す
  def following?(user)
    following_user.include?(user)
  end	 
end

<has_many 上から2行>
has_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy

  • has_many :follower, class_name: "Relationship" 
    この記述は、Userモデルでの説明と一緒!
    (Userモデルでの表記と一緒で、relationshipモデルも分岐しているので、class_nameが必要です)

  • foreign_key: "follower_id"
    参照先外部キーのカラムを指定する必要があるため、foreign_keyの記述が必要。
    これは以下の二つの記述()部分を成立するためにある。
    ⚪︎(フォローする側から、) 中間テーブルを通して、フォローされる側を取得する
    ⚪︎(フォローされる側から、)中間テーブルを通して、フォローしてくる側を取得する

<has_many 下の2行>
has_many :following_user, through: :follower, source: :followed
ここではforeign_keyで繋いだところの定義をおこなう。

  • following_user:中間テーブルを通し、followedモデルのフォローされる側を取得
  • follower_user:中間テーブルを通し、followerモデルのフォローする側を取得

Routingの設定

Userと、Relationshipは associationして繋げているため、userのidが必要。
relationshipsをネストさせる。

routing のネストとは

あるコントローラへのルーティングの記述の中に、別のコントローラへのルーティングを記述すること。

routingをネストすることのメリット

  • URLの改装構造ができる。
  • 関係性のあるもの同士をを紐づけることができる。

関係があるもの同士は、原則ルーティングをネストさせて関係性のあるURLを生成すること!!!!

今回はこのようになる。

:
resources :users, only: [:index, :show, :edit, :update] do
    member do
      get :follows, :followers
    end
      resource :relationships, only: [:create, :destroy]
  end

rails routesで確認するとこのようにidが入っていることが確認できる。

memberオプション

resources以外の自分で定義したアクションへのルーティングを設定する場合に使用する。
同じ場面で使用するものとしてcollectionがあるが、アクションにidを渡したいときは、 memberを使用すること。

  • member : アクションにidが渡されるため、id を使用した特定のデータに対するアクションの場合
  • collection :idを渡さない。id の必要ない全体のデータに対するアクションの場合

上記でmemberを使用しているのは、
follow・followerアクションが必要かつ、それらにはidが必要だから。

Controllerの作成

今回はrelationships controller
$ rails g controller relationships

コントローラーにアクションの記述

< Relationships Controller >

class RelationshipsController < ApplicationController

  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
end

< Users Controller >

def follows
  user = User.find(params[:id])
  @users = user.following_user.page(params[:page]).per(3).reverse_order
end

def followers
  user = User.find(params[:id])
  @users = user.follower_user.page(params[:page]).per(3).reverse_order
end

⚠️ ユーザー詳細ページで、フォロー数とフォロワー数を表示するための追記も行う。
< Users Controller >

:
def show
  @user = User.find(params[:id])
 # @posts = @user.posts.page(params[:page]).reverse_order
  @following_users = @user.following_user
  @follower_users = @user.follower_user
end
:

Viewの記述を行う。

  • まずはこの部分。
<table>
:
省略
:
<tr>
    <th>Follows</th>
    <td>
      <%= link_to follows_user_path(user) do %>
        <h6><%= user.follower.count %></h6>
      <% end %> 
    </td>
  </tr>
  <tr>  
    <th>Followers</th>
    <td>  
      <%= link_to followers_user_path(user) do %>
        <h6><%= user.followed.count %></h6>
      <% end %>
    </td>
  </tr> 
</table>
<!--ここからはボタンの記述-->
<div class='row mb-4'>
  <% if current_user != user %>
    <% if current_user.following?(user) %>
    <%= link_to 'フォロー外す', user_relationships_path(user.id), method: :delete, class: "btn-sm btn-danger" %>
    <% else %>
    <%= link_to 'フォローする', user_relationships_path(user.id), method: :post, class:"btn-sm btn-success" %>
    <% end %>
  <% end %>
</div>

  • Follow User-page/followers User-pageも作成する。
    View>Userフォルダの下に、follows.html.erb と  followers.html.erbファイルを作成すること。

View>Userフォルダの下に、follows.html.erb と  followers.html.erbファイルを作成すること。

<follows.html.erb>

<table class='table'>
      <thead>
        <tr>
          <th>Image</th>
          <th>name</th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <% @following_users.each do |user| %>
          <tr>
            <td>
              <%= link_to user_path(user) do %>
                <%= image_tag user.get_profile_image, size:'50x50' %>
              <% end %>
            </td>
            <td><%= user.name %></td>
            <td>
              <%= link_to follows_user_path(user) do %>
                <h6>フォロー<%= user.follower.count %></h6>
              <% end %>
            </td>
            <td>  
              <%= link_to followers_user_path(user) do %>
                <h6>フォロワー<%= user.followed.count %></h6>
              <% end %>
            </td>
          </tr>
        <% end %>
        </tbody>
    </table> 

このように作成することができる!!!!

<ここで私の少し躓いたポイント>
follow usersページで、followしている人たちのfollow人数の表記.

<% @following_users.each do |user| %>
    <h6>フォロー<%= user.follower.count %></h6>
     <h6>フォロワー<%= user.followed.count %></h6>	
<% end %>	     

user.follower.count
   => each文での変数 userにmodelで定義したfollowerで出力して.countオプション!

参照ページ

⏩ association公式ドキュメント
Railsでサポートされている関連付け
request.refererについて
memberオプション
routingのネスト

Discussion

がんもがんも

説明がとてもわかりやすくて理解しやすかったです!
こういう絵があると内容が入ってくるし、絵が上手!!
勉強になったし参考にさせてもらいました♪
ありがとうございました(^^)

AirichanAirichan

参考にしていただきありがとうございます!
(今改めて見ると、不足が多いかなあと思いながら...笑)
がんもさんの図はとても見やすいし、動画もつけていて見やすかったです!
のちのDMM受講生は、同じように、がんもさんの記事でとても助かると思います!!

がんもがんも

そう言っていただけると嬉しいです!ありがとうございます😄
あいりさんみたいな手書きの絵が描けるようになりたいなぁ...といつも読みながら思ってます笑
後から読み直して改善点が見つけられるって成長している証拠なのですごいと思います😊
先の記事がどんどんクオリティがあがっているので読んでいくのが楽しみです😌

AirichanAirichan

このように当時を振り返ることができるのも毎日やってた醍醐味かなと思ったりします!
初心者の時期だからこその記事というか,考え方というか,視点というか...(まだ初心者ですが)笑
がんもさんのも楽しみです😉💖