0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsでフォロー機能を実装した(Ajax通信)

Posted at

#概要
SNSなどユーザーを扱うアプリケーションではフォロー機能などはよく扱われる。Railsにてフォロー機能を実装する際は、多対多の関連付けをしてユーザー通しを関連付けます。
今回、ユーザー一覧画面(index)とユーザー詳細画面(show)にてフォロー機能を実装した。
また、ユーザビリティを考慮し、Ajax通信にてフォロー機能が使用できるようにした。

#前提

  • Userモデルは作成済み
  • JQueryはインストール済み

#1.Relationshipモデルの作成
ユーザー通しを多対多の関連付けを行うためRelationshipモデルを作成します。RelationshipモデルはUserモデルの中間テーブルとして作成し、Userモデルのidを扱います。

terminal
rails g model Relationship
create_relationships.rb
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モデルを編集します。

relationship.rb
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テーブルを参照するようにします。

user.rb
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)も同じ設定です。

user.rb
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のコントローラーを作成します。

terminal
rails g controller relationships

ルーティングの設定をします。フォローフォロワー一覧はUserとの絡みがあるため、user内にfollowings、followersアクションのルーティングを設定します。

routes.rb
resources :users do
  member do
    get :followings, :followers
  end
end
resources :relationships, only: [:create, :destroy]

フォローフォロワー一覧はUserとの絡みがあるため、user内にfollowings、followersアクションのルーティングを設定します。
relationshipsへのアクションはcreateとdestroyのみ設定します。

relationshipsコントローラーを作成します。

relationships_controller.rb
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コントローラーにフォローフォロワー一覧用メソッドを追加します。

users.rb
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を設定します。

index.html
<% @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 %>
show.html
<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名を設定します。

続いてフォローボタンも作成します。

index.html.erb
<% @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 %>
show.html.erb
<% 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の値を渡します。

フォロー、フォロワーボタンを作成します。

follow.html.erb
<%= form_with(model: current_user.following_relationships.build) do |f| %>
  <%= f.hidden_field :following_id, value: user.id %>
  <%= f.submit "フォローする", class: "~" %>
<% end %>
unfollow.html.erb
<%= 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へ向かいます。

create.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:"~" %>');
destroy.js.erb
$("#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を用いた非同期フォロー機能の実装

ありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?