【rails】フォロー機能。追記あり
整理していなかったのでまとめておきます。
参考:第14章 ユーザーをフォローする - Railsチュートリアル
※devise導入済みですすめます。
この機能を導入したら
①view側にフォロー中とフォロワーの数の表示
②フォローボタンの作成
③実際にフォロー中の人、フォロワーを表示
この3つをview側に追加することを目指して作っていきます。
導入前に
twitterのようなフォロー機能は非対称性があります。つまり自分がフォローしていたとしても、
フォローが返されない、とう言う状況もあります。
このことを頭に入れなければいけません。
モデルの導入
rails generate model Relationship follower_id:integer followed_id:integer
そして、 できたファイルに
class CreateRelationships < ActiveRecord::Migration[5.0] def change create_table :relationships do |t| t.integer :follower_id t.integer :followed_id t.timestamps end add_index :relationships, :follower_id add_index :relationships, :followed_id add_index :relationships, [:follower_id, :followed_id], unique: true end end
これでfollower_idとfollowed_idの組み合わせが必ずユニークであることを保証します。
マイグレーションファイルに書いてある add_index - Qiita
データベースにindexを張る方法 - Qiita
DBのインデックスと複合インデックス - Qiita
次に$ rails db:migrate
関連付け
userとreleationshipは1対多の関係にあります。
class User < ApplicationRecord has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy end
いつもの関連付けなら、has_many :relationships
とすることろを、自分がフォローした(能動関係)を作るためにhas_many :active_relationships
にして、
次に、has_many :active_relationships
というモデルは無いので
class_name: "Relationship"
と書いて、Railsに探して欲しいモデルのクラス名を明示的に伝える。
更に、外部キーを先程作った foreign_key: "follower_id",
で指定します。
(ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要があります。
そのため、関連付けにdependent: :destroyも追加しています。)
class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end
本来ならbelongs_to :user
、外部キーがuser_id
としますが、今回は仮想的にフォローする人される人に分けたいのでfollower_id
、followed_id
として関連付けます。
belongs_to :follower, class_name: "User"
とすることで
userと関連付けるのをfollower_id
と指定します。
また、follower
、followerd
というクラスは存在しないのでclass_name: "User"
として明記します。
このように書くと、今回はuser_id
がfollwer_id
、followed_id
として認識されます。
つまり、relationshipモデルのfollower_idやfollowed_idとuserモデルのidがひも付きます。
この結果、下記のメソッドが使えるようになります。
active_relationship.follower フォロワーを返します active_relationship.followed フォローしているユーザーを返します user.active_relationships.create(followed_id: other_user.id) userと紐付けて能動的関係を作成/登録する user.active_relationships.create!(followed_id: other_user.id) userを紐付けて能動的関係を作成/登録する (失敗時にエラーを出力) user.active_relationships.build(followed_id: other_user.id) userと紐付けた新しいRelationshipオブジェクトを返す
バリテーションの追加
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
現時点では、フォローするuserとの1対多の関係ができました。この時点で②フォローボタンの作成 のみならほとんどてきてしまいます。
多対多
一人のユーザーはいくつもフォローするしされる、つまり多対多の関係にあります。
class User < ApplicationRecord has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed #追加したのを和訳すると userはactive_relationshipsを通ってfollowingをたくさん持っている。 さらに細かく言うと、外部キーがfollower_idと紐付いているuserはactive_relationshipsを通ってfollowed_idをたくさん持っている end
Active Record の関連付け - Railsガイド
:sourceパラメーター を使って、「following配列の元はfollowed idの集合である」ということを明示的にRailsに伝えます。
この結果、user.following
でユーザーがフォロー中の人がわかります。 つまりfollowed_id
を抜き出すことができます。
さらに、user.following.count
でフォロー中の人数がわかるようにます。
follow、unfollow、following?メソッドを作成
followingで取得した集合をより簡単に取り扱うために、followやunfollowといった便利メソッドを追加します。
class User < ApplicationRecord # ユーザーをフォローする #followed_idにother_userを追加 def follow(other_user) following << other_user end # ユーザーをフォロー解除する # followed_idを削除 def unfollow(other_user) active_relationships.find_by(followed_id: other_user.id).destroy end # 現在のユーザーがフォローしてたらtrueを返す def following?(other_user) following.include?(other_user) end end
フォロワー側
今まではすべて自分からフォローした側でしたが、自分をもフォローされます。
しかし、これはいままでとすべて反対のことをすればいいだけです。
class User < ApplicationRecord has_many :microposts, dependent: :destroy 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 has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower end
この結果、user.followers
でユーザーをフォロワーがわかります。 つまりfollower_id
を抜き出すことができます。
さらに、user.followers.count
でフォロワー数がわかるようにます。
view側
①view側にフォロー中とフォロワーの数の表示
②フォローボタンの作成
③実際にフォロー中の人、フォローされた人を表示
をいよいよ作っていきます。
①view側にフォロー中とフォロワーの数の表示
touch app/views/devise/shared/_stats.html.slim
この中にフォロー中とフォロワーの数の表示を書きます。
#app/views/devise/shared/_stats.html.slim - @user ||= current_user .stats | フォロー中のユーザー = link_to following_user_path(@user) div#following = @user.following.count | フォロワーリスト = link_to followers_user_path(@user) div#followers =@user.followers.count
そして、 表示させます。
#app/views/users/show.html.slim(わたしの場合) = render 'devise/shared/stats'
これで、①view側にフォロー中とフォロワーの数の表示 ができました。 また、
= link_to following_user_path(@user) = link_to followers_user_path(@user)
次にこのリンクから③実際にフォロー中の人、フォローされた人を表示のviewを作っていきます。
③実際にフォロー中の人、フォローされた人を表示
まずはルーティングから
config/routes.rb Rails.application.routes.draw do resources :users do member do get :following, :followers end end end
このURLで下記のようになります。
HTTPリクエスト URL アクション 名前付きルート GET /users/1/following following following_user_path(1) GET /users/1/followers followers followers_user_path(1)
次に、コントローラー
class UsersController < ApplicationController 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_follow' end end
render 'show_follow
を2つのアクションで使い回しても@users
の中身は違うので問題有りません。
show_follow
のviewを作っていきます。
touch app/views/users/ahow_follow.htnl.slim
#follow_show .container - @users.each do |user| .user_content = link_to user_path(user) = user.username #←.usernameはuserモデルの中のカラム名に合わせてください
②フォローボタンの作成
これで最後です。
まずはコントローラーを作っていきます。
rails generate controller Relationships
次にルーティングです。
フォロー関係を作るか、消すかだけなので
Rails.application.routes.draw do resources :users do member do get :following, :followers end end resources :relationships, only: [:create, :destroy] end
次に、コントローラーを書きます
#app/comtroller/relationships_controller.rb class RelationshipsController < ApplicationController def create @user = User.find(params[:followed_id]) current_user.follow(@user) redirect_to @user end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) redirect_to @user end end
さらにボタンを作っていきます。
フォローするボタン
touch app/views/relationships/_follow.html.slim
=button_to current_user.active_relationships.build, params: { followed_id: @user.id } | フォローする
フォローを解除するボタン
touch app/views/relationships/_unfollow.html.slim
=button_to current_user.active_relationships.find_by(followed_id: @user.id), method: :delete | 解除する
そして上記のボタンをまとめます。
touch app/views/users/_follow_form.htnl.slim
- unless @user == current_user #follow_form - if current_user.following?(@user) = render 'relationships/unfollow' - else = render 'relationships/follow'
最後にこれを表示させます。
#app/views/users/show.html.slim(わたしの場合) = render 'devise/shared/stats' = render 'users/follow_form'
おしまい。