#前提
ログイン機能はDeviseで作成済み(user)
seeds.rbにuserのデータ先に入れておきます。
user1 = User.create({ nickname: '田中', email: 'test1@test.com', password: '********' })
user2 = User.create({ nickname: '森山', email: 'test2@test.com', password: '********' })
user3 = User.create({ nickname: '坂下', email: 'test3@test.com', password: '********' })
id | nickname | password | |
---|---|---|---|
1 | 田中 | test1@test.com | ******** |
2 | 森山 | test2@test.com | ******** |
3 | 坂下 | test3@test.com | ******** |
#モデル作成
$ rails g model Relationship
relationshipテーブルにuserモデルの外部キー(user_id)を追加
class CreateRelationships < ActiveRecord::Migration[5.0]
def change
create_table :relationships do |t|
t.references :user, type: :bigint, foreign_key: true
t.references :follow, foreign_key: { to_table: :users }
t.timestamps
t.index [:user_id, :follow_id], unique: true
end
end
end
・references 外部キーの追加
(参照モデル名_idというカラム名で参照モデルのidと紐付け。これだけだと参照にないidでも登録できてしまう)
・foreign_key: true
外部キー制約(親レコードにない値は参照カラムに登録できない、has_manyやbelongs_toなどを考える上で、「関連付けできないような値は絶対に来ない」という意味。user_idにはusers.idにある値しか入れられない)
・foreign_key: { to_table: :users }
外部キー制約【応用編】(user_id以外の名前(follow_id)でusers_idへの外部キー制約をはる。foreign_key: trueにすると存在しないfollowsテーブルを参照してしまう)
・unique: true
(user_idとfollow_id のペアで重複するものが保存されないようにするデータベースの設定)
$ rails db migrate
foreign_keyを記述してrails db:migrateをすると、
foreign_keyを記述した側のモデル(relationship.rb)に自動的にbelongs_toが記述されます。
(has_manyに関してはもう片方のモデル.rbに手で記述しなければなりません)
#関連付け(アソシエーション)多対多
class Relationship < ApplicationRecord
belongs_to :user
belongs_to :follow, class_name: 'User'
validates :user_id, presence: true
validates :follow_id, presence: true
end
・class_name: 'User'
補足設定(followモデルなんて存在しない→userモデルに紐付け)
class User < ApplicationRecord
has_many :relationships, dependent: :destroy
has_many :followings, through: :relationships, source: :follow
has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
has_many :followers, through: :reverse_of_relationships, source: :user
end
・has_many :relationships
(relationshipsモデルと関連付け)
・has_many :followings, through: :relationships, source: :follow
(relationshipsテーブルのfollow_idを参照して、followingsモデル(架空)にアクセス)
・has_many :reverse_of_relationships
(has_many :relationshipsの逆方向。フォロワー(フォローされているuser))
・class_name: 'Relationship'
(relationshipsモデルに紐付け)
・foreign_key: 'follow_id'
外部キー(relationshipsテーブルにアクセスする時、follow_idを入口とする)
・has_many :followers, through: :reverse_of_relationships, source: :user
(followersモデル(架空)。中間テーブルはreverses_of_relationshipで持って帰ってくる情報はuser_id)
#userモデルにフォローメソッドの追加
...
# 自分以外だったらフォロー
def follow(other_user)
unless self == other_user
relationships.find_or_create_by(follow_id: other_user.id)
end
end
# フォローがあればアンフォロー
def unfollow(other_user)
relationship = relationships.find_by(follow_id: other_user.id)
relationship&.destroy
end
# フォローしているユーザー
def following?(other_user)
followings.include?(other_user)
end
・other_user
(他ユーザーのページでフォロー/アンフォローボタンを押した人)
・unless self == other_user
(フォローしようとしている(other_user)が自分自身(self)ではないか)
・find_or_create_by
(findが先に実行、探してみて無ければ作成。同じデータは2つ作られることはない)
・followings.include?(other_user)
(followings によりフォローしている User 達を取得、include?(other_user) によって other_user が含まれていないかを確認)
・include?
(followingsの中にother_userオブジェクトが含まれていればtrueを返す)
#コントローラー作成
$ rails g controller relationships
class RelationshipsController < ApplicationController
before_action :set_user
def create
following = current_user.follow(@user)
if following.save
@user.create_notification_follow!(current_user)
flash[:success] = 'ユーザーをフォローしました'
redirect_back(fallback_location: root_path)
else
flash.now[:alert] = 'ユーザーのフォローに失敗しました'
redirect_back(fallback_location: root_path)
end
end
def destroy
following = current_user.unfollow(@user)
if following.destroy
flash[:success] = 'ユーザーのフォローを解除しました'
redirect_back(fallback_location: root_path)
else
flash.now[:alert] = 'ユーザーのフォロー解除に失敗しました'
redirect_back(fallback_location: root_path)
end
end
private
def set_user
@user = User.find(params[:follow_id])
end
end
#ルーティング
resources :relationships, only: [:create, :destroy]
#部分テンプレート作成
<% unless current_user == user %>
<% if current_user.following?(user) %>
<%= form_for(current_user.relationships.find_by(follow_id: user.id), html: { method: :delete }) do |f| %>
<%= hidden_field_tag :follow_id, user.id %>
<%= f.submit 'Unfollow', class: 'btn btn-dark btn-block' %>
<% end %>
<% else %>
<%= form_for(current_user.relationships.build) do |f| %>
<%= hidden_field_tag :follow_id, user.id %>
<%= f.submit 'Follow', class: 'btn btn-dark btn-block w-100' %>
<% end %>
<% end %>
<% end %>
フォローボタンを表示させたいところで呼び出す
<%= render ‘relationships/follow_button’, user: @user %>
#一覧表示(フォローフォロワー人数、フォローボタン付き)
<% if @user.followers.present? %>
<% @users.each do |user| %>
<%= link_to user_path(user) do %>
<%= attachment_image_tag user, :image, fallback: "logo.png" %>
<% end %>
<%= link_to user.nickname, user_path(user) %>
フォロー(<%= user.followings.count %>人)<br>
フォロワー(<%= user.followers.count %>人)
<%= render 'relationships/follow_button', user: user %>
<% end %>
<% else %>
フォロワーがいません
<% end %>
def followers
@user = User.find(params[:id])
@users = @user.followers.all
end
※followingsも同様。
#データ投入
# user1をuser2,3がフォロー
user1.followers << user2
user1.followers << user3
user1.save
# user2をuser1,3がフォロー
user2.followers << user1
user2.followers << user3
user2.save
備忘録、復習です。