FreeCamp      

【Rails】フォロー・フォロワー機能の実装

Twitterのようなアプリケーションを作成するにあたって
必要になる機能の中にフォロー・フォロワー機能があります。
この記事ではそのフォロー・フォロワー機能を出来るだけわかりやすく説明していきます。

目次

  1. フォロー・フォロワー機能とは
  2. 関連付けについて
  3. フォローしている・されている状態
  4. 実装
  5. ソースコード

1. フォローフォロワー機能とは

SNS等で一度はこの機能を利用したことがあると思います。
ユーザーAがユーザーBをフォロー、
ユーザーBがユーザーAをフォローすれば
ユーザーAもユーザーBもお互いにフォローしている状態であり、どちらもフォロワーの状態です。
ユーザーAがユーザーBをフォローしていればユーザーAからするとユーザーBはフォローで、
ユーザーBからするとユーザーAはフォロワーです。
以降フォローするユーザーを「follower」、フォローしているユーザーのことを「followed」と呼びます。

2. 関連付けについて

フォローとフォロワーについて機能実装するときに一番頭を使うのは関連付けのイメージを頭に浮かべることでしょう。
ブラウザで「rails フォロー フォロワー」と検索すると様々な記事が出てきますが、
説明がややこしかったり、とりあえずできたから記事に載せていたりとしていることが多くあり、
本質的に理解できていないと思います。
プログラミングは「イメージ」です。
頭の中で「イメージ」さえ浮かべばあとは調べて実装するだけです。
話がずれてしまいましたが、それではフォロー・フォロワーについて「イメージ」を湧かせていきましょう。

3. フォローしている・されている状態

さて、フォローしている状態とはどのような状態なのでしょうか。
Userモデル一つでは関係を構築できないため、ここではRelationshipモデルを利用していきます。

このようにid: 1, 2, 3のUserが存在し、
Relationshipにはfollower_idとfollowed_idカラムがあります。
基本的に作成するモデルはこの二つです。

follower_idがフォローをする方、フォローされている側からはフォロワーと呼びます。
逆に、followed_idはフォローされる方です。
この関係性から、1は2, 3を2は3をフォローしています。
また、2は1に3は1, 2にフォローされています。

ここで一つフォロー・フォロワー機能を作成する上でのポイントです。

フォローする人は「follower」
フォローされる人は「followed」

と覚えてください。
世の中のフォロー・フォロワー機能の記事を見ると
following, followed, follow, followerなど混在していて、
複数の記事を見ているとイメージが湧きづらく、混乱してしまいます。
ですので、この記事を見た方は他の記事を見るのはオススメしません。
大事なことなのでもう一度。

フォローする人は「follower」
フォローされる人は「followed」

と覚えてください。

4. 実装

ここから実際に実装していきます。
まずはできることを確認し、そこから「理解」していきましょう。

$ rails new rails_relationship
$ cd rails_relationship
$ rails g controller users index
...追記
gem 'devise'
$ bundle
$ rails g devise:install
$ rails g devise User
$ rails db:migrate

ルーティングは以下のようにします。

devise_for :users
root 'users#index'

ユーザーの新規登録・ログイン・ログアウトができるように、
application.html.erbのbodyタグ直下に以下を記述しましょう。

<header>
  <nav>
    <ul>
      <% unless user_signed_in? %>
        <li><%= link_to '新規登録', new_user_registration_path %></li>
        <li><%= link_to 'ログイン', new_user_session_path %></li>
      <% else %>
        <li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete %></li>
      <% end %>
    </ul>
  </nav>
</header>

ここまできたら新規登録・ログイン・ログアウトができるか試しましょう。
できましたか?

それではフォローフォロワー機能を実装していきましょう。
まずは関連付けのためにmodel/user.rbを編集します。

...追記
has_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy # フォロー取得
has_many :followed, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy # フォロワー取得

一行目は、ユーザーとフォローする人を結びつけています。
Relationshipモデルのfollower_idにuser_idを格納する宣言です。
class_name, foreign_keyは下記記事がとても参考になります。

https://qiita.com/tomoharutsutsumi/items/e548186c763079327ed1

二行目は、ユーザーとフォローされる人を結びつけています。
Relationshipモデルのfollowed_idにuser_idを格納する宣言です。
RelationshipモデルからUserモデルを呼び出すことも有り得るため、
Relationshipモデルにも関連付けを追記しましょう。

...追記
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"

そして、自分がフォローしているユーザーと自分をフォローしているユーザーを簡単に取得するためにthroughを使った関連付けも追記しましょう。

...追記
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
$ rails g controller relationships
...追記
def follow
  current_user.follow(params[:id])
  redirect_to root_path
end

def unfollow
  current_user.unfollow(params[:id])
  redirect_to root_path
end
...追記
post 'follow/:id' => 'relationship#follow', as: "follow" # フォローする
post 'unfollow/:id' => 'relationship#unfollow', as: "unfollow" # フォロー外す

そして、viewにもフォローしているかの確認・フォローする・フォロー外すリンクも付け加えましょう。

<% if user_signed_in? %>
  <h1>ユーザー一覧画面</h1>
  <% @users.each do |user| %>
    <p>
      <%= "id: #{user.id} email: #{user.email}" %>
      <% if current_user.following?(user) %>
        <%= link_to 'フォロー外す', unfollow_path(user.id), method: :POST %>
      <% else %>
        <%= link_to 'フォローする', follow_path(user.id), method: :POST %>
      <% end %>
    </p>
  <% end %>
<% end %>

ここまでできたらアプリケーションを起動し、ユーザーを二人作成してください。
フォローしているかの確認・フォローリンク・フォロワーリンクがうまく実装されましたか?

それではユーザーの詳細ページを作成していきます。
コントローラ・ビュー・ルーティングに追加・作成していきます。

...追記
def show
  @user = User.find(params[:id])
end
...追記
resources :users, only: :show

users/show.html.erbを作成します。

... 編集
<h2>ユーザー詳細ページ</h2>
<p><%= "id: #{@user.id}" %></p>
<p><%= "フォロー数: #{@user.follower.count}" %></p>
<p><%= "フォロワー数: #{@user.followed.count}" %></p>
<% unless @user == current_user %>
  <% if current_user.following?(@user) %>
    <%= link_to 'フォロー外す', unfollow_path(@user.id), method: :POST %>
  <% else %>
    <%= link_to 'フォローする', follow_path(@user.id), method: :POST %>
  <% end %>
<% end %>
<p></p>

<h3>フォロー一覧</h3>
  <% @user.following_user.where.not(id: current_user.id).each do |user| %>
    <p>
      <%= "id: #{user.id} email: #{user.email}" %>
      <% if current_user.following?(user) %>
        <%= link_to 'フォロー外す', unfollow_path(user.id), method: :POST %>
      <% else %>
        <%= link_to 'フォローする', follow_path(user.id), method: :POST %>
      <% end %>
      <%= link_to "show", user_path(user) %>
    </p>
  <% end %>
<h3>フォロワー一覧</h3>
  <% @user.follower_user.where.not(id: current_user.id).each do |user| %>
    <p>
      <%= "id: #{user.id} email: #{user.email}" %>
      <% if current_user.following?(user) %>
        <%= link_to 'フォロー外す', unfollow_path(user.id), method: :POST %>
      <% else %>
        <%= link_to 'フォローする', follow_path(user.id), method: :POST %>
      <% end %>
      <%= link_to "show", user_path(user) %>
    </p>
  <% end %>
...編集
<% if user_signed_in? %>
  <p><%= link_to "マイページへ", user_path(current_user) %></p>
  <h2>ユーザー一覧画面</h2>
  <% User.all.where.not(id: current_user.id).each do |user| %>
    <p>
      <%= "id: #{user.id} email: #{user.email}" %>
      <% if current_user.following?(user) %>
        <%= link_to 'フォロー外す', unfollow_path(user.id), method: :POST %>
      <% else %>
        <%= link_to 'フォローする', follow_path(user.id), method: :POST %>
      <% end %>
      <%= link_to "show", user_path(user) %>
    </p>
  <% end %>
<% end %

5. ソースコード

https://github.com/ssshhhooota/rails_relationship