Railsで「いいね！」機能を作る - ①アソシエーションに別名をつける

### 背景
最近初学者さんにプログラミングを解説する機会があるのですが、使っているテキストに、まあまあ難しそうだなー。と思える記述がありました。

流暢に解説できるか不安なところもあったので、勉強がてら、噛み砕いてまとめてみようと思います。何回かに分けて作成していきますので、記事は徐々に増やしていきます。

▼続編出しました！
[Railsで「いいね！」機能を作る - ②「いいね！」のcreateアクション](https://qiita.com/tanutanu/items/bff95ce3a91dab1eb2c6)
[Railsで「いいね！」機能を作る - ③「いいね！」を解除できるようにする](https://qiita.com/tanutanu/items/89cd52ed57b487fed84d)

▼番外編
[RailsでAjaxで「いいね！」機能を実装する。](https://qiita.com/tanutanu/items/b435af08aff706e01f2a)

### データ構造
基本のデータ構造は以下の通り。
Twitterみたいなアプリで、ユーザー(`users`）はたくさんの投稿(`posts`)を持っている。そして、自分の投稿も含む全ての投稿に「いいね！」(`likes`)を付けられる。という感じです。

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

シンプルな構造なのですが、後半、ちょっとひねりが必要でした。

### アソシエーションを書き起こしてみる
まずは、Railsの規則通りにシンプルに書けるところからアソシエーション(つながり・関係性)を書き起こしてみます。

```Ruby:models/user.rb
class User < ApplicationRecord
  has_many :posts
  has_many :likes
end
```
```Ruby:models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes
end
```
```Ruby:models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
end
```

とてもシンプルですね:slight_smile:
この時、例えば`controller`で以下のように書くと、

```ruby:controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @posts = User.find(params[:user_id]).posts
  end
end
```
**「そのユーザーの投稿一覧」**を取得できます。（今回は例としてユーザー詳細ページで一覧表示する形にしています）

### 問題点
この時、例えば**「そのユーザーが『いいね！』した投稿一覧」**を表示したいと思ったときには、少し工夫が必要です。

おそらく、下記`@favorite_posts`のように記載すれば**「そのユーザーが『いいね！』した投稿一覧」**は取得できるように予想しますが、

```ruby:controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @posts = User.find(params[:user_id]).posts
    @favorite_posts = User.find(params[:user_id]).likes.posts
    # ↑こちらはあくまでも予想のコード、実際に動作しません。
  end
end
```
`User`モデルで、`has_many :posts`は既に使われていて、

```ruby:models/user.rb(再掲)
class User < ApplicationRecord
  has_many :posts
  has_many :likes
  # has_many :posts, through: :likes とは書けない。
end
```
`has_many :posts, through: :likes`と書くと、名前が重複してしまうからです。
ちなみに、`through:`を使わないで、`has_many :posts`から[「いいね！」した投稿を取得する方法](https://kimuraysp.hatenablog.com/entry/2017/09/05/235816)もあるにはあるのですが、

難しい上に、データの読み出しにコストがかかるので、お勧めはしません。

### アソシエーションに別名をつけて解決
そんな時は、`user`→`likes`→`posts`という関係の流れに別名をつけて解決します。
つまり、下記の図の赤枠で囲ったアソシエーション（つながり）に別名を付ければいいのです。

![er.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393888/48663eb5-3d59-5781-3bd9-70e0310da8f6.png)

別名の付け方は下記の通りです。

```ruby:models/user.rb
class User < ApplicationRecord
  has_many :posts
  has_many :likes
  has_many :favorites, through: :likes, source: :post
end
```

上記のコードでは`user`→`likes`→`posts`というアソシエーションに、`favorites`という別名をつけています。

ただし、そのままだとrailsは`favorites`というテーブルを探してしまうので、`source: :post`というオプションをつけて`posts`テーブルを参照するようにします。

上記のように書くことで、`user.favorites`と記載すると、**「ユーザーが『いいね！』した投稿」**を取得することができます。（下記は、こんな感じで使えますよーというサンプルです:slight_smile:）

```ruby:controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    # ↓ユーザーの投稿一覧
    @posts = User.find(params[:user_id]).posts
    # ↓ユーザーがいいねした投稿一覧
    @favorite_posts = User.find(params[:user_id]).favorites
  end
end
```

早い人は、プログラミング初心者から数週間でこの内容を理解して卒業していくんですね。すごいなー。

私も自信持って教えられるように、次は「いいね！」をする、「やめる」のコードについても研究したいと思います。

▼続きはこちら
[Railsで「いいね！」機能を作る - ②「いいね！」のcreateアクション](https://qiita.com/tanutanu/items/bff95ce3a91dab1eb2c6)

#### 追記：逆の関係（投稿に「いいね！」したユーザーを取得）
なお、投稿に「いいね！」したユーザー（`post`→`like`→`user`のアソシエーション）は次のような記述で定義することができます。

```ruby:models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  
  has_many :likes
  has_many :users, through: :likes
end
```
先ほどつけた別名(favorites)は意識しなくてOKです。

なぜなら`ユーザー(user)`と`投稿(post)`の関係は、

- `user`が自分の作った投稿をもつ`post belongs_to user`の関係と、
- `postが`自分に「いいね！」をしたユーザーを持つ`post has_many users through likes`という二つの関係があるので

それそのまま記載すればOKです＾＾