railsフォローフォロワー機能の実装、一覧表示、seedに初期データ投入

#前提

ログイン機能はDeviseで作成済み（user）

seeds.rbにuserのデータ先に入れておきます。

```Ruby:db/seeds.rb
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 | email | password |
|:-:|:-:|:-:|:-:|
|  1|  田中 |  test1@test.com | ********|
|  2|  森山 |  test2@test.com | ********|
|  3|  坂下 |  test3@test.com | ********|

#モデル作成

```
$ rails g model Relationship
```

relationshipテーブルにuserモデルの外部キー(user_id)を追加

```db/migrate/000000000000_create_relationships.rb

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に手で記述しなければなりません）

#関連付け（アソシエーション）多対多

```app/models/relationship.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モデルに紐付け）


```app/models/user.rb
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モデルにフォローメソッドの追加

```app/models/user.rb
...
# 自分以外だったらフォロー
 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
```

```Ruby:app/controller/relationships_controller.rb
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

```

#ルーティング

```Ruby:config/routes.rb
 resources :relationships, only: [:create, :destroy]
```

#部分テンプレート作成

```erb:app/view/relationships/_follow_button.html.erb
<% 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 %>
```


フォローボタンを表示させたいところで呼び出す

```erb
<%= render ‘relationships/follow_button’, user: @user %> 
```

#一覧表示(フォローフォロワー人数、フォローボタン付き)
```erb:followers.html.erb
<% 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 %>
```

```Ruby:app/controller/users_controller.rb
  def followers
    @user = User.find(params[:id])
    @users = @user.followers.all
  end
```

※followingsも同様。


#データ投入

```Ruby:db/seeds.rb
# user1をuser2,3がフォロー
user1.followers << user2
user1.followers << user3
user1.save

# user2をuser1,3がフォロー
user2.followers << user1
user2.followers << user3
user2.save
```

備忘録、復習です。