Edited at

フォロー機能、完成版

More than 3 years have passed since last update.


relationshipモデルの作成

$ rails g model Relationship follower_id:integer following_id:integer


20160220133958_create_relationships.rb(マイグレーションファイル)にインデックスとか追加


20160220133958_create_relationships.rb


def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :following_id

t.timestamps null: false
end
add_index :relationships, :follower_id
add_index :relationships, :following_id
add_index :relationships, [:follower_id, :following_id], unique: true
end
end


add_index :relationships, [:follower_id, :following_id], unique: true

これは一度フォローしたユーザーを2度フォローしてしまわないようにするための一意の設定。


UserとRelationshipの関連付け

まず、フォローするのとフォローされるのとで能動的関係と受動的関係で分けることをする。

ここでは能動的関係をactive_relationshipと呼び、受動的関係をpassive_relationshipと呼ぶことにする。

userモデルが2つあり(本当は一つだけど仮想的に2

つと考えている)、その間にrelationshipモデルが挟まっていて、フォローしている人とされている人の関係を仲介している状態を常に想像すると良い。


最初にactive_relationshipの方から進めていく

まずはユーザーAがfollowすること(following)のみを考える。(1対多)

ユーザーAはBさんCさんDさんなどのたくさんのユーザーをactive_relationshipsモデルを通してフォローできる。


user.rb


has_many :active_relationships,class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy



relationship.rb


belongs_to :follower, class_name: "User"


一応説明を入れておくが、relationshipsテーブルには、following_idとfollower_idのペアがあって初めてrelationshipの関係が成立する。

今回は、

Aさんがfollowingしようとしているので、Aさんがrelationshipsテーブルにfollowing_idを登録する役割。

has_many :active_relationshipsだと、railsが外部キーにactive_relationships_idを探してしまい、ダメなので外部キーを明示的に示してあげる必要がある。BさんCさんDさんが今回はfollower_idを登録する役割。


こうすればこれだけのメソッドが使えるようになる。

active_relationship.follower フォロワーを返す

active_relationship.following フォローしているユーザーを返す

user.active_relationships.create(following_id: user.id) userを紐付けて能動的関係を作成/登録する

user.active_relationships.create!(following_id: user.id) userを紐付けて能動的関係を作成/登録する (失敗時にエラーを出力)

user.active_relationships.build(following_id: user.id) userと紐付けた新しいRelationshipオブジェクトを返す


次に、フォローされることのみを考える。(1対多)

この際、relationshipモデルをpassive_relationshipモデルとして置き換える。

ユーザーAをフォローしている人はたくさんいるからhas_many。

ユーザーAをフォローしている人にとってfollowingの対象はユーザーA1人だからbelongs_to。


relationship.rb


belongs_to :following, class_name: "User"



user.rb


has_many :active_relationships,class_name: "Relationship", foreign_key: "following_id", dependent: :destroy


フォローされるユーザーAをfollower_idを外部キーを指定して特定する。


フォローしているユーザーの集団を取り出す

1人のユーザーにはいくつもの「フォローする/される (多対多)」のリレーションシップがある。

なのでhas_many throughを使う。同じモデルを別の見方をするので、同じuserモデルに、対応した2つのhas_many throughを書かなければいけないからややこしいから注意。


user.rb


has_many :following, through: :active_relationships, source: :following

railsはfollwingを見てrelationshipsテーブルのfollowing_idを使って対象のユーザーの集合を取得してくる。

source:はなくても良いがわかりやすくするために書居ておいた。

実際はマジでいらない。

これでuser.followingが使えるようになる。

この関連付けにより、フォローしているユーザーを配列の様に扱えるようになった。

例えば、include?メソッドを使ってフォローしているユーザーの集合を調べてみたり、関連付けを通してオブジェクトを探しだせるようになった。

user.following.find(other_user)でフォローしているユーザーを取り出せるようになった。


あるユーザーをフォローしている集団(フォロワー)を取り出す

次に、user.followingと対になるuser.followersメソッドを作成する。


user.rb


has_many :followers, through: :passive_relationships, source: :follower


これで、user.followersメソッドが使えるようになってfollowersの集団が取り出せるようになった。

#followやunfollowなどのメソッドの追加

followingで取得した集合をより簡単に取り扱うためにメソッドを作成する。


user.rb


# ユーザーをフォローする
def follow(other_user)
active_relationships.create(following_id: other_user.id)
end

# ユーザーをアンフォローする
def unfollow(other_user)
active_relationships.find_by(following_id: other_user.id).destroy
end

# 現在のユーザーがフォローしてたらtrueを返す
def following?(other_user)
following.include?(other_user)
end



user.followersメソッドを作成する

これは上のuser.followingメソッドと対になるやつ。

follower_idとfollowing_idを入れ替えるだけで、フォロワーについてもユーザーのフォローのときとまったく同じ方法が使用できる。

followerなので、フォローされていると考え、受動的関係と考え、passive_relationshipと考える。


user.rb


has_many :passive_relationships, class_name: "Relationship", foreign_key: "following_id", dependent: :destroy

has_many :followers, through: :passive_relationships, source: :follower


これで、フォローする、される機能ができた。


フォロー、あんフォローボタンの作成

まず、メソッド定義から。


user.rb

# ユーザーをフォローする
def follow(other_user)
active_relationships.create(following_id: other_user.id)
end

# ユーザーをアンフォローする
def unfollow(other_user)
active_relationships.find_by(following_id: other_user.id).destroy
end

# 現在のユーザーがフォローしてたらtrueを返す
def following?(other_user)
following.include?(other_user)
end


次に、フォローするボタンを表示させるボタンを作成する


show.html.erb


<%= render 'follow_form' if logged_in? %>


もしログインしていたら、

follow_formページを表示するようにしている。


_follow_form.html.erb


<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>


もしログインしているユーザーが別のユーザーをフォローしているならunfolowへ、

もしフォローしていないならフォローへ。


_follow.html.erb


<%= form_for(current_user.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :following_id, @user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>



_unfollow.html.erb


<%= form_for(current_user.active_relationships.find_by(following_id: @user.id),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>


これで、followを送信するボタンができた。

見てわかる通り、ボタンは1つで、

そのユーザーが別のユーザーをフォローしていないならばフォローボタンを、

フォローしているならアンフォローボタンを表示させるようにしている。


ルーティングの設定


routes.rb


resources :users do
member do
get :following, :followers
end
end


following_user GET /users/:id/following(.:format) users#following

followers_user GET /users/:id/followers(.:format) users#followers

このルーティングができる。


followerとfollowingの表示

まず、ユーザーの詳細ページで自分のfollowingとfollowersの表示させるものを作る。


show.html.erb


<section class="stats">
<%= render 'users/stats' %>
</section>



stats.html.erb


<% @user ||= current_user %>
<div class="stats">
<a href="<%= following_user_path(@user) %>">
<strong id="following" class="stat">
<%= @user.following.count %>
</strong>
following
</a>
<a href="<%= followers_user_path(@user) %>">
<strong id="followers" class="stat">
<%= @user.followers.count %>
</strong>
followers
</a>
</div>


これで、ユーザー詳細画面で自分のフォローフォロワー数が表示されるようになった。


次に、followingページとfollowerページを表すための定義をする。


users_controller.rb


def following
@user = User.find(params[:id])
@users = @user.following
render 'show_follow'
end

def followers
@user = User.find(params[:id])
@users = @user.followers
render 'show_follower'
end


ユーザー詳細ページのfollowingとfollowersを押した時に、

render 'show_follow'はのちに作るshow_follow.html.erbに飛ぶようにしている。

render 'show_follower'はのちに作るshow_follower.html.erbに飛ぶようにしている。


次に、フォローページとフォロワーページの詳細を作る。


show_follow.html.erb


<% @user.following.each do |user| %>
<table>
<tr>
<td> <%= user.name %> </td>
<td><%= link_to '詳細', user_path(user) %></td>
</tr>

</table>
<% end %>



show_follower.html.erb


<% @user.followers.each do |user| %>
<table>
<tr>
<td> <%= user.name %> </td>
<td><%= link_to '詳細', user_path(user) %></td>
</tr>

</table>
<% end %>


これで無事、フォローの集団とフォロワーの集団を取り出したものを表示するページを作成することができた。

これでひととおり、フォローの作成はできた。