##記事作成理由
1:プログラミング初学者の私が見返すため。
2:私のような初学者の学習に少しでも役立てればと思ったため。
※誤った記述や知識があればお知らせいただけると幸いです。
##開発環境
・ruby: 2.6.3
・rails: 5.2.4.5
・OS: macOS Catalina ver10.15.7
・Cloud9
##前提条件
・Userモデル,usersテーブルがある
・Bootstrap導入済み
・deviseを使ってログイン機能実装済み
・CRUD機能の基礎について学習済み
##実装の流れ
1:Relationshipモデルの作成
2:中間テーブル(relationshipsテーブル)の作成
3:アソシエーションの設定
4:アクションの定義
5:Viewファイルに記述
###1:Relationshipモデルの作成
rails g model Relationship
###2:中間テーブル(relationshipsテーブル)の作成
マイグレーションファイルにカラムを追加
class CreateRelationships < ActiveRecord::Migration[5.2]
def change
create_table :relationships do |t|
t.references :follower, foreign_key: {to_table: :users}
t.references :followed, foreign_key: {to_table: :users}
t.timestamps
t.index [:follower_id, :followed_id], unique: true
end
end
end
rails db:migrate
マイグレーションファイルの反映忘れずに!
####ーー解説ーー
relationshipsテーブルの中身はこのようになります。
カラム | タイプ | オプション |
---|---|---|
follower_id | integer | foreign_key: {to_table: :users} |
followed_id | integer | foreign_key: {to_table: :users} |
relationshipsテーブルは中間テーブルであるため、
followerカラムとfollowedカラムはt.references
をつけて生成します。
(ポイント!)
今回生成したカラム (follower_id と followed_id) は自由に命名することができます。
他記事ではfollowing_idやfollows_idなど様々な命名がされています。自分が1番理解しやすいように命名しましょう。
(色々な記事を調べて、様々な命名を見ると頭が混乱します。僕だけかもしれませんが・・・)
また、follower、followedどちらも外部キーとして設定します。 foregin_key(外部キー)
ここでforegin_key :true
としてしまうと、followersテーブルやfollowedsテーブルなど存在しないテーブルを参照してしまいエラーになります。
それを避けるために{to_table: :users}
として、usersテーブルのidを参照するよう紐づかせています。
t.index [:follower_id, :followed_id], unique: true
これにより、2つのカラムの中身が同じ組み合わせでデータを保存することを防ぎます。
=同じユーザーを2回フォローできないようにしています。
中間テーブル概念についてはこちら2つの記事がとても参考になりました。
[【Railsでフォロー機能を作ろう】]
(https://hajimeteblog.com/rails-follow/)
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル
###3:アソシエーションの設定
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
####ーー解説ーー
■2,3行目
relationshipsテーブルでforeign_keyとしてfollower_id , followed_id を設定しているため、モデル名もFollower
, Followed
とします。
しかし、FollowerモデルもFollowedモデルも実在しない為、class_name: "User"
とオプションを追記し、関連するモデル(本当のモデル名)を指定します。
__※__このとき、"User"は文字列扱いになるため、ダブルクォーテーションで囲んでいます。
■4,5行目
validatesの説明は割愛します。
(わからない方は「バリデーション」)でお調べください。
class User < ApplicationRecord
# フォローするユーザーから見た中間テーブル
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
# 中間テーブルactive_relationshipsを通って、フォローされる側(followed)を集める処理をfollowingsと命名
# フォローしているユーザーの情報がわかるようになる
has_many :followings, through: :active_relationships, source: :followed
# 中間テーブルpassive_relationshipsを通って、フォローする側(follower)を集める処理をfollowingsと命名
# フォローされているユーザーの情報がわかるようになる
has_many :followers, through: :passive_relationships, source: :follower
end
####ーー解説ーー
ここが鬼門です。がんばっていきましょう。
4つのhas_manyがありますが、上から2つずつに分けて説明します。
まず中間テーブルの見方を2つに分けます。
1:フォロー__する__ユーザーから見た視点
2:フォロー__される__ユーザーから見た視点
本来であれば has_many :relationships とすればいいのですが、
見方が2つあるため、2通りの定義をしなければなりません。
そのため、ここでは
フォロー__する側__を active_relationships
と命名し、(__2__行目)
フォロー__される側__を passive_relationships
と命名しています。(__7__行目)
↓
※先ほどuser.rb
で作成したモデル(Follower,Followed)と同様、これらも好きに命名することができます。
他記事を見て比較する時など、命名が異なるケースがあるため、注意しましょう。
そして、参照元のモデルはどちらもRelationshipモデル
であるため、
class_name: "Relationship"
とオプションを追加します。
しかし、このままではRelationshipsテーブルのどちら(follower , followed)を参照すればいいのかがわかりません。
そこで、下記のように指定してあげます
active_relationships
は、foreign_key: "follower_id"
を参照
passive_relationships
は、foreign_key: "followed_id"
を参照
dependent: :destroy
は、とあるUserが削除された時にそれに紐づくrelationshipのデータも削除されるようにしています。
次に、こちらの文ですが、
has_many :followings, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
これらはコードのコメントに記載の通り、定義したテーブルを通って(through)、sourceで指定したモデルの参照元からデータを集めることができます。
これにより
@user.followings
(userがフォローしている人の情報)
や
@user.followers
(userがフォローされている人の情報=userをフォローしている人の情報)
といった表記が可能になります。
※くどいようですが、、followings
やfollowers
も好きに命名することができます。
他記事を見て比較する時など、命名が異なるケースがあるため、注意しましょう。
これでアソシエーションは__完成__です。
###4:アクション定義
user.rb と relationships.controller.rbにアクションを定義します。
#下記3つのアクションを追記
class User < ApplicationRecord
# フォローする
def follow(user_id)
active_relationships.create(followed_id: user_id)
end
# フォローを外す
def unfollow(user_id)
active_relationships.find_by(followed_id: user_id).destroy
end
# すでにフォローしているのか確認
def following?(user)
followings.include?(user)
end
end
class RelationshipsController < ApplicationController
before_action :authenticate_user!
def create
current_user.follow(params[:user_id])
redirect_to request.referer
end
def destroy
current_user.unfollow(params[:user_id])
redirect_to request.referer
end
# フォローしている人一覧
def follower
user = User.find(params[:user_id])
@users = user.followings
end
# フォローされている人一覧
def followed
user = User.find(params[:user_id])
@users = user.followers
end
end
###5:Viewファイルに記述
ここからはあくまで例になります。
定義している変数などによって記述の仕方は変わります。
※一部抜粋
ーー__フォロー数、フォロワー数表示、フォローボタン__ーー
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= attachment_image_tag user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg", size:"40x40" %></td>
<td><%= user.name %></td>
<td><%= "フォロー数: #{user.active_relationships.count}" %></td>
<td><%= "フォロワー数: #{user.passive_relationships.count}" %></td>
<td>
<% if current_user.id != user.id %>
<% if current_user.following?(user) %>
<%= link_to 'フォローを外す', user_relationships_path(user.id), method: :DELETE %>
<% else %>
<%= link_to 'フォローする', user_relationships_path(user.id), method: :POST %>
<% end %>
<% end %>
</td>
<td><%= link_to "Show", user_path(user.id) %></td>
</tr>
<% end %>
</tbody>
ーー__フォローしている人一覧__ーー
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= attachment_image_tag user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg" , size:"40x40" %></td>
<td><%= user.name %></td>
<td>フォロー数:<%= user.followings.count %></td>
<td>フォロワー数:<%= user.followers.count %></td>
<td><%= link_to "show", user_path(user.id) %> </td>
</tr>
<% end %>
</tbody>
ーー__フォローされている人一覧__ーー
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= attachment_image_tag user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg" , size:"40x40" %></td>
<td><%= user.name %></td>
<td>フォロー数:<%= user.followings.count %></td>
<td>フォロワー数:<%= user.followers.count %></td>
<td><%= link_to "show", user_path(user.id) %> </td>
</tr>
<% end %>
</tbody>
以上です。 閲覧ありがとうございました!
誤字、脱字、誤り等ございましたら教えていただけると幸いです。