Railsでお気に入り機能の実装方法をまとめてみる(初心者向け)

#概要
お気に入り機能を実装しようと思うと毎度毎度混乱してしまう残念おつむ野郎なので、自分用の備忘録としてまとめておく。
当方初心者なので初心者のお役にも立てればいいなと思ったり思わなかったり。
この記事ではuserが投稿したpostへお気に入り登録ができるようにする。

全体的な流れは、
①中間テーブル(favoriteモデル)の作成
②userモデルとpostモデルの多対多の実現
③お気に入り機能用のメソッドの作成
という感じ。
まとめてみるとやること少ねーな。

##①中間テーブルの作成
```Rails
rails g model favorite user:references post:references
```
毎度おなじみのrails gコマンドでfavoriteコマンドを作る。
データ型にはreferencesを使う。これで今回作ったfavoriteモデルにはuser_idカラムとpost_idカラムが作られ、後外部キー制約もついてくるんで便利かも。
これでできるマイグレーションファイルがこれ。

```ruby:migration
class CreateFavorites < ActiveRecord::Migration[5.0]
  def change
    create_table :favorites do |t|
      t.references :user, foreign_key: true
      t.references :post, foreign_key: true

      t.timestamps
    end
  end
end
```
ここでrails db:migrate……といきたいところだけれでもしばし待て。

```diff:migration
class CreateFavorites < ActiveRecord::Migration[5.0]
  def change
    create_table :favorites do |t|
      t.references :user, foreign_key: true
      t.references :post, foreign_key: true

      t.timestamps
+     t.index [:user_id, :post_id], unique: true
    end
  end
end
```
書き足したコードは、use_idとpost_idのペアが重複するのを防ぐためのもの。
同じ投稿を何度もお気に入り登録できないようにするぜ、って感じ。
これでマイグレーションファイルの完成。
rails db:migrateを実行すると中間(favorite)テーブルの完成！

##②userモデルとpostモデルの多対多の実現
ユーザーの投稿に対してお気に入り機能を実装しようというので、当然この段階では投稿機能は完成しているものとする。投稿機能では、多分userとpostで一対多の構造になっていると思うので、ここではそれを前提にする。
まずさっき作ったfavoriteモデルがこれ。

```ruby:favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :post
end
```
userモデルとpostモデルとそれぞれ一対多の関係になっているのが分かる。
ここはいじる必要なしなのでそのまま放置。

```ruby:post.rb
class Post < ApplicationRecord
  has_many :favorites
  has_many :users, through: :favorites
end
```
has_many :favoritesでfavoriteテーブルと繋がる。
has_many :user, through: :favoritesでfavoriteモデルを経由してuserテーブルと繋がる。

```ruby:user.rb
class User < ApplicationRecord
  has_many :posts
  has_many :favorites
  has_many :fav_posts, through: :favorites, source: :post
```
userモデルでもやることはpostモデルと同じだけどちょっとここに曲者がいる。
投稿機能でuserとpostで一対多の関係を作っている場合、has_many :postsがある。
お気に入り用のhas_many :postsを作ろうとすると、名前がまったく同じであるためエラーになってしまう。そのため、お気に入り機能ではpostsではなく別の名前を与えてあげる必要がある。

```ruby:user.rb
has_many :fav_posts, through: :favorites, source: :post
```
fav_postsはfavoriteのpost_idを使ってpostsテーブルと繋がる、的な感じの意味。
fav_postsなんてテーブルはないのでsourceでどのテーブルを参照するのかを指定してあげる。
これで多対多は終わり。
まとめているとなんだか簡単な気がしてきた。

##③お気に入り機能用のメソッドの作成
お気に入り登録のためのメソッドを作る。
登録と削除ができればいいかなと思ってる。
userモデルかpostモデルに書くかでやり方が変わってくると思うけど、個人的に分かりやすいuserモデルの方で実装しようと思う。
ますは登録用のメソッドから作っていく。

```ruby:user.rb
def like(post)
  favorites.find_or_create_by(post_id: post.id)
end
```
そんなに難しいことはやってないとは思う。その難しくないことをいつまでたっても覚えられない奴が言うのもなんだけど。
お気に入り登録しようとしている投稿がすでにないかを調べ、なければ中間テーブルに新しく作るぜ、って感じ。
これで中間テーブルにuser_idとpost_idが新しく保存される。
お気に入り登録はこれで以上なので、問題がないかrails consoleで試してみるのが良し。
以下一応確認のやり方。

```ruby
user.like(post)
```

お気に入りする人.like(お気に入りされる投稿)
そのまんまやな。
これでエラーがでなければＯＫ

```ruby:user.rb
 def unlike(post)
   favorite = favorites.find_by(post_id: post.id)
   favorite.destroy if favorite
 end
```
最後にお気に入りの削除機能を作る。

```ruby
favorite = favorites.find_by(post_id: post.id)
```

削除しようとしている投稿を検索。

```ruby
favorite.destroy if favorite
```

検索した投稿が見つかればそれを削除。
これもrails consoleで試しておくと良い。

お気に入り機能はこれで完成。
実際にビューから操作するにはもちろんコントローラーをいじってビューにリンクつけたりしないといけないけど、とりあえず今回は機能のみということで。
以上！

##最後に
お気に入り機能だけじゃなく、多対多の構造を用いるとき、初心者は(自分もだけど)参照しているテーブルが中間テーブルか相手のテーブルなのかを強く意識した方が良いと思う。でないと本気で混乱する。
中間テーブルを参照するときは新しく作るときや削除するときなど、つまり中間テーブルごと根こそぎ何かをやりたいとき。相手のテーブルを参照するのは取得するとき。みたいな感じだろうか？

間違いや不備があればご指摘お願いします。