Railsでユーザーフォロー機能を実装する（Ajax使うよ）①

## 何をしたか
Railsの課題を実施しています。`User`のフォロー機能を実装することになりました。
この機能は前に実装したことはあって、**「確かあのアソシエーションが難しいやつ」**ぐらいには覚えていました。

たまたま、この機能の実装を人に教える機会もありそうだったので、しっかり教えられるように手順を細かく記すことにしました。

ただ、かなりの長文になってしまったので前後編に分けています。この記事ではDB設計〜アソシエーションまでの話をして、続きは下記の記事になります。

[Railsでユーザーフォロー機能を実装する（Ajax使うよ）②](https://qiita.com/tanutanu/items/5f41ece86e9d2b70bae4)

なお、実行環境は以下の通りです。

- `Rails 5.2.3`
- `Ruby 2.6.0`

## ゴール

今回、作るものはユーザーのフォロー機能です。ボタンを押すと、ユーザーをフォロー、もう一度ボタンを押すと、フォロー解除できます。

[![Image from Gyazo](https://i.gyazo.com/3bc3c8d87d9eb1c460d1894f6ba32783.gif)](https://gyazo.com/3bc3c8d87d9eb1c460d1894f6ba32783)

また、この機能のために作成したモデルは下記の通りです。

[![Image from Gyazo](https://i.gyazo.com/e6915dc3624821fd7b502bbbef8b8a71.png)](https://gyazo.com/e6915dc3624821fd7b502bbbef8b8a71)

謎すぎる形態をしていますよね:sweat_smile:
いきなり、上記のモデルに行き着くのは難しいので、ひとつひとつ分解して考えていきます。

なお、以下はあくまでも**「私はこう考えた」**考え方ですので、人によっては他の考え方の方がしっくりくるかもしれません。

## 実装
### 分解して考える

まずは、「ユーザー」と「フォロワー」という登場人物を作った方がわかりやすいので、
`User`モデルを**（頭の中で）**`User`モデルと`Follower`モデルに分解します。

[![Image from Gyazo](https://i.gyazo.com/e8651a29fdc07e39a069bae0ac73eddb.png)](https://gyazo.com/e8651a29fdc07e39a069bae0ac73eddb)

この中で、一番簡単そうな**Userがたくさんの人をフォローしている**状況から考えていきましょう。

[![Image from Gyazo](https://i.gyazo.com/4ad451c066a5a154be065ff0413dd02c.png)](https://gyazo.com/4ad451c066a5a154be065ff0413dd02c)
この場合、`User`モデルに対し

```Ruby
has_many: followings, through: :relationships
```

というアソシエーションを**（頭の中で）**仮置きします。ただし、**Userモデル = Followerモデル**であることは忘れないでください。


上記の関係を、マイグレーションファイルで表すとこうなります。

```Ruby:db/migrate/XXXXXXXX_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.2]
  def change
    create_table :relationships do |t|
      t.references :user
      t.references :follower, foreign_key: { to_table: :users }

      t.timestamps

      t.index [:user_id, :follower_id], unique: true
    end
  end
end
```

`followers`テーブルは実際には存在しないので、`foreign_key: { to_table: :users }`で、**「followerを探すときはusersテーブル(のfollower_id)を見てね」**とRailsに伝えています。

この辺りの実装は、↓この記事が大変参考になりました。

[マイグレーションにおいて参照先テーブル名を自動で推定できないカラムを外部キーとして指定する方法](https://qiita.com/kymmt90/items/03cb9366ff87db69f539)


また、同じ人を2回フォローできないように

```Ruby
t.index [:user_id, :follow_id], unique: true
```
`user_id`カラムと`follower_id`のカラムの組み合わせに、重複した値が入らないようにする制約を加えています。

### アソシエーションの記載

#### Usersモデル
この時、`User`モデルのアソシエーションの記述は下記のようになります。

```Ruby:models/user.rb
class User < ApplicationRecord
  has_many :relationships, dependent: :destroy
  has_many :followings, through: :relationships, source: :follower
end
```
ポイントは、以下の通りです。

- `has_many :followings`で、[アソシエーションに別名](https://qiita.com/tanutanu/items/2a27f1e503c4cbf30928)をつける
- `source: :follower`で、`follower`モデルを見てねとRailsに教える

若干複雑になってきましたね...:frowning2:
難しそうなところにはリンクも貼りましたので、ひとつづつ読み解いてみてください。

ところで、`Follower`モデルなんてないのに、どうやって`Follower`モデルを参照するの？？という件については、次の`Relationship`モデルのアソシエーションをご覧ください。

#### Relationshipモデル

`Relationship`モデルの記載内容は下記の通りです。

```Ruby:models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: 'User'
end
```
このように記載することで、`Follower`モデルの参照がきたら`User`モデルを参照するようにRailsに伝えています。

#### 余談： `source`と`class_name`の違いについて

ところで、さっきは`source`で別のモデルを参照したのに、今度はなんで`class_name`なの？って思いませんでしたか？（私は思いました）

その理由は、こちらに書いてあって、
[Rails: difference between :source => ?? and :class_name => ?? in models](https://stackoverrun.com/ja/q/3670942)

- **source** ... `has_many :****, through: :****`の時に使う
- **class_name** ...`has_many :***`の時に使う

だそうです:smile:知らなかった！！

### 確認

ここで、一回きちんとアソシエーションが定義できているか確かめることをお勧めします。私は、こんなふうに確かめました。

```sh
$ rails c
> user = User.first
> user.relationships
# => []
> user.relationships.create!(follower_id: 2)
# => Relationship のレコードが表示
> user.followings
# => フォローしているUser(user_idが２のユーザー)のレコードが表示
```

フォロー中のユーザーのレコードが呼び出されたので、`id:1`のユーザーは`id:2`のユーザーをフォローできていると言えます。

### 逆の関係も考える

さて、それでは今度は**ユーザーがたくさんのフォロワーにフォローされている**という、先程どは逆の状況も考えてみたいと思います。

まず、ゴールなのですが、**先程の逆の矢印を定義できれば良いです。**
具体的に図に書き込んでみます。

[![Image from Gyazo](https://i.gyazo.com/8c75b3e4b7286561344235493beea412.png)](https://gyazo.com/8c75b3e4b7286561344235493beea412)

`follower`がたくさんの`user`を`relationships`を通じて持っている、こんな図が出来上がります。

でも、**Followerモデルは実際には存在せず、Userモデルとイコール**でしたね。
そのため、**主語をUserに書き換えてみます。**

[![Image from Gyazo](https://i.gyazo.com/c5ea565be49dda72d62d13f77366d62d.png)](https://gyazo.com/c5ea565be49dda72d62d13f77366d62d)

`User`は`relationships`を通じて、`followers`をたくさん持っている。
言葉としてはわかりやすいのですが、今度は、頭の中に作った`Follow`モデルが邪魔になってきました。

`followers`テーブルを置いたために考えるのが難しくなってきているので、`Follow`モデルと`User`モデルを一つにまとめました。

[![Image from Gyazo](https://i.gyazo.com/36e133bbc2bc11c7e13ef262b08fa1de.png)](https://gyazo.com/36e133bbc2bc11c7e13ef262b08fa1de)

だいぶ、一番初めに提示したER図に近づいてきましたね:grin:

ついでに、先程の赤い矢印も復活させてみました。
すると、今度は**`through: :relationships`の部分で、名前が重複している**のがわかります。

[![Image from Gyazo](https://i.gyazo.com/b7431f7c39d5e9772bfe42273c58ba07.png)](https://gyazo.com/b7431f7c39d5e9772bfe42273c58ba07)

そこで、今度は片方の名前を変えて、`user`は`follower`にフォローされている、と言う意味で`passive_relationships`と名付けました。

これで、最初に提示したER図と同じになっています:relaxed:

### アソシエーションの記載

#### Userモデル
では、この関係を実際にモデルに表していきます。
まず、`User`モデルはこうなります。

```Ruby:models/user.rb
class User < ApplicationRecord
  has_many :relationships, dependent: :destroy
  has_many :followings, through: :relationships, source: :follower

  has_many :passive_relationships, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy # 追記1
  has_many :followers, through: :passive_relationships, source: :user # 追記2
end
```
めっちゃ複雑になってきましたね:joy:ひとつひとつ読みといていくと、まず、追記１の部分は

- `User`はたくさんの`passive_relationships`を持っています。
- この時、参照して欲しいクラスは`Relationship`です。
- 外部キーとして`follower_id`を使います。

というのを表しています。
次に、追記２の部分は

- `User`はたくさんの`followers`を、`passive_relationships`を通じて持っています。
- `passive_relationships`は、直前の定義により`Relationship、`クラスを参照するようになっています。

複雑すぎて憤死しそうです:joy:
ワンステップずつ丁寧に書いたつもりなので、丁寧に読んでみてください:sweat_smile:

#### Relarionshipモデル

最後に、`Relationship`モデルに追記した内容を紹介します。

```Ruby:models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :user # 追記
  belongs_to :follower, class_name: 'User'
end
```
こちらは逆にシンプルすぎて心配になるレベルですが、こちらはこれで完了です:innocent:

### 確認

ここで、1回確認をしてみます。先程、コンソールで**id:1のユーザーがid:2のユーザーをフォローする**と言うデータは作りましたので、**id:2のユーザーは、id:1のユーザーにフォローされている**はずです。

それを、コンソールで試してみます。

```sh
$ rails c
> user2 = User.second
> user2.followers
# => id:1のUserのレコードが表示される
```
user2をフォローしているユーザーが取得できました！！成功です:smile:

## to be contenued ...
これからいよいよviewやcontrollerを実装！...となっていくのですが、
ここからは次回に続きます。

Ajax出てこないじゃん！と思った方、ごめんなさい。次回に出てきます。。。

▼この話の続編はこちら
[Railsでユーザーフォロー機能を実装する（Ajax使うよ）②](https://qiita.com/tanutanu/items/5f41ece86e9d2b70bae4)