#概要
SNSなどユーザーを扱うアプリケーションではフォロー機能などはよく扱われる。Railsにてフォロー機能を実装する際は、多対多の関連付けをしてユーザー通しを関連付けます。
今回、ユーザー一覧画面(index)とユーザー詳細画面(show)にてフォロー機能を実装した。
また、ユーザビリティを考慮し、Ajax通信にてフォロー機能が使用できるようにした。
#前提
- Userモデルは作成済み
- JQueryはインストール済み
#1.Relationshipモデルの作成
ユーザー通しを多対多の関連付けを行うためRelationshipモデルを作成します。RelationshipモデルはUserモデルの中間テーブルとして作成し、Userモデルのidを扱います。
rails g model Relationship
class CreateRelationships < ActiveRecord::Migration[5.2]
def change
create_table :relationships do |t|
t.references :follower, foreign_key: { to_table: :users }
t.references :following, foreign_key: { to_table: :users }
t.timestamps
end
add_index :relationships, [:follower_id, :following_id], unique: true
end
end
"references :〇〇"と設定することで〇〇モデル(テーブル)のidを〇〇_idというカラム名に自動的に設定することができます。このままではfollowerやfollowingテーブルという存在しないテーブルを参照してしまうため、外部キー"foreign_key: { to_table: :users }"と設定し、Userテーブルを参照するように設定します。
ユーザー通しの関連性を保存するように設定し、同じ組み合わせを保存しないように"unique:true"と設定します。
設定後、db:migrateでRelationshipテーブルを作成します。
#2.モデルの編集
続いてRelationshipモデルとUserモデルを編集します。
belongs_to :follower, class_name: "User"
belongs_to :following, class_name: "User"
validates :follower_id, presence: true
validates :following_id, presence: true
"belongs_to :follower"と設定しますが、followerテーブルは存在しないため"class_name: "User""でUserテーブルを参照するようにします。
has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship", dependent: :destroy
has_many :followings, through: :following_relationships
has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship", dependent: :destroy
has_many :followers, through: :follower_relationships
"has_many :following_relationships"でidを取得しますが、"following_relationships_id"は存在しないので、"class_name: "Relationship""でrelationshipテーブルを参照、"foreign_key: follower_id"で外部キーを設定し、"follower_id"を取得するよう設定します。"dependent: :destroy"でUserが削除された際、relationshipのidも削除するようになります。
”has_many :followings, through: :following_relationships"でfollowing_relationshipsを通して、ユーザーidを取得します。
逆(follower)も同じ設定です。
def following?(other_user)
self.followings.include?(other_user)
end
def follow(other_user)
self.following_relationships.create(following_id: other_user.id)
end
def unfollow(other_user)
self.following_relationships.find_by(following_id: other_user.id).destroy
end
フォロー機能に関するメソッドを作成します。"following?"はユーザーがフォロー済みであるかを確認します。"follow"と"unfollow"はそれぞれフォロー、アンフォローを行うメソッドです。
#3.コントローラの作成
relationshipのコントローラーを作成します。
rails g controller relationships
ルーティングの設定をします。フォローフォロワー一覧はUserとの絡みがあるため、user内にfollowings、followersアクションのルーティングを設定します。
resources :users do
member do
get :followings, :followers
end
end
resources :relationships, only: [:create, :destroy]
フォローフォロワー一覧はUserとの絡みがあるため、user内にfollowings、followersアクションのルーティングを設定します。
relationshipsへのアクションはcreateとdestroyのみ設定します。
relationshipsコントローラーを作成します。
class RelationshipsController < ApplicationController
def create
@user = User.find(params[:relationship][:following_id])
current_user.follow(@user)
respond_to do |format|
format.html {redirect_back(fallback_location: root_path)}
format.js
end
end
def destroy
@user = User.find(params[:relationship][:following_id])
current_user.unfollow(@user)
respond_to do |format|
format.html {redirect_back(fallback_location: root_path)}
format.js
end
end
end
フォロー、アンフォロー動作を受け取った時の動作を設定します。
Ajax通信にて動作を処理するため、"respond_to do |format|"にてフォーマットを確認し、レンダリング先を指定します。format.jsにて該当するjsリクエストがあれば、〜js.erbにて処理します。htmlでは"redirect_back"にて直前のページを表示し、表示できなければroot_pathに向かいます。
usersコントローラーにフォローフォロワー一覧用メソッドを追加します。
def followings
@user = User.find(params[:id])
@users = @user.followings.page(params[:page]).per(10)
render "show_followings"
end
def followers
@user = User.find(params[:id])
@users = @user.followers.page(params[:page]).per(10)
render "show_followers"
end
注:ユーザー一覧表示にページネーションを使用しています。
#4.ビューの編集
フォローフォロワー一覧へのリンクを作成します。各ユーザー数は表示できるようにしておきます。Ajax通信にて処理するため、該当箇所を-div id="~"-で囲い、idを設定します。
<% @users.each do |user| %>
~省略~
<div id="following_count_<%= user.id %>">
<%= link_to "フォロー中(#{user.followings.count})", followings_user_path(user),class:"~" %>
</div>
<div id="follower_count_<%= user.id %>">
<%= link_to "フォロワー(#{user.followers.count})", followers_user_path(user),class:"~" %>
</div>
<% end %>
<div id="following_count">
<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>
</div>
<div id="follower_count">
<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>
</div>
indexでは各ユーザーに対してidを振り分けるため、
"-div id="following_count_<%= user.id %>"-"
"-div id="follower_count_<%= user.id %>"-"
としてid名を設定します。
続いてフォローボタンも作成します。
<% @users.each do |user| %>
~省略~
<% if current_user && user != current_user %>
<div id="follow_form_<%= user.id %>">
<% if current_user.following?(user) %>
<%= render "unfollow", user: user %>
<% else %>
<%= render "follow", user: user %>
<% end %>
</div>
<% end %>
<% end %>
<% if current_user && @user != current_user %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render "unfollow", user: @user %>
<% else %>
<%= render "follow", user: @user %>
<% end %>
</div>
<% end %>
一覧表示と同様、indexでは各ユーザーに対してidを振り分けるため、"-div id="follow_form_<%= user.id %>"-"としてid名を設定します。
"user: @user"にてレンダリング先のuserへ@userの値を渡します。
フォロー、フォロワーボタンを作成します。
<%= form_with(model: current_user.following_relationships.build) do |f| %>
<%= f.hidden_field :following_id, value: user.id %>
<%= f.submit "フォローする", class: "~" %>
<% end %>
<%= form_with(model: current_user.following_relationships.find_by(following_id: user.id), method: :delete) do |f| %>
<%= f.hidden_field :following_id %>
<%= f.submit "フォローをやめる", class: "~" %>
<% end %>
form_withを使用してリクエストを送信します。form_withはデフォルトでAjax通信をするよう設定されています。もしform_forを使う場合は、オプションに"remote: true"を設定します。
"f.hidden_field :following_id"にてユーザーidを入れるようにします。
#5.jsファイルの作成
フォームから送信されたリクエストはコントローラのアクションにて処理を行い、js.erbへ向かいます。
$("#follow_form_<%= @user.id %>").html("<%= j(render partial: 'users/unfollow', locals: { user: @user }) %>");
$("#follow_form").html("<%= j(render partial: 'users/unfollow', locals: { user: @user }) %>");
$("#following_count_<%= @user.id %>").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count_<%= @user.id %>").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
$("#following_count").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
$("#follow_form_<%= @user.id %>").html("<%= j(render partial: 'users/follow', locals: { user: @user }) %>");
$("#follow_form").html("<%= j(render partial: 'users/follow', locals: { user: @user}) %>");
$("#following_count_<%= @user.id %>").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count_<%= @user.id %>").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
$("#following_count").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
"$("設定したid").html('レンダリングしたいhtml')"という記載でAjax通信にて部分的にレンダリングすることができます。
注: "や'の差で動作がうまくいかない箇所があったため、設定には注意が必要です。私は上記の記述で動作しました。
#まとめ
以上で、Ajax通信にてフォロー機能を実装することができました。
ユーザー詳細(show)でのフォロー機能は参考文献があり、簡単にできましたが、一覧(index)にてフォロー機能を設定するには参考文献がなく、かなり苦戦しました。試行錯誤して無事達成できたので、良い勉強と経験になりました。
#参考文献
[Ruby on Rails] フォロー機能を実装しよう
【Rails】Ajaxを用いた非同期フォロー機能の実装
ありがとうございました。