#relationshipモデルの作成
$ rails g model Relationship follower_id:integer following_id:integer
#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モデルを通してフォローできる。
has_many :active_relationships,class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
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。
belongs_to :following, class_name: "User"
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を書かなければいけないからややこしいから注意。
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メソッドを作成する。
has_many :followers, through: :passive_relationships, source: :follower
これで、user.followersメソッドが使えるようになってfollowersの集団が取り出せるようになった。
#followやunfollowなどのメソッドの追加
followingで取得した集合をより簡単に取り扱うためにメソッドを作成する。
# ユーザーをフォローする
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と考える。
has_many :passive_relationships, class_name: "Relationship", foreign_key: "following_id", dependent: :destroy
has_many :followers, through: :passive_relationships, source: :follower
これで、フォローする、される機能ができた。
#フォロー、あんフォローボタンの作成
まず、メソッド定義から。
# ユーザーをフォローする
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
次に、フォローするボタンを表示させるボタンを作成する
<%= render 'follow_form' if logged_in? %>
もしログインしていたら、
follow_formページを表示するようにしている。
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
もしログインしているユーザーが別のユーザーをフォローしているならunfolowへ、
もしフォローしていないならフォローへ。
<%= 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 %>
<%= 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つで、
そのユーザーが別のユーザーをフォローしていないならばフォローボタンを、
フォローしているならアンフォローボタンを表示させるようにしている。
#ルーティングの設定
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の表示させるものを作る。
<section class="stats">
<%= render 'users/stats' %>
</section>
<% @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ページを表すための定義をする。
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
に飛ぶようにしている。
#次に、フォローページとフォロワーページの詳細を作る。
<% @user.following.each do |user| %>
<table>
<tr>
<td> <%= user.name %> </td>
<td><%= link_to '詳細', user_path(user) %></td>
</tr>
</table>
<% end %>
<% @user.followers.each do |user| %>
<table>
<tr>
<td> <%= user.name %> </td>
<td><%= link_to '詳細', user_path(user) %></td>
</tr>
</table>
<% end %>
これで無事、フォローの集団とフォロワーの集団を取り出したものを表示するページを作成することができた。
これでひととおり、フォローの作成はできた。