[Rails] has_and_belong_to_many で多対多を実装

Railsガイドを眺めていたら
`has_and_belong_to_many`なる記述を見つけたので確認してみる。
[has-and-belongs-to-many関連付け-Railsガイド]
(https://railsguides.jp/association_basics.html#has-and-belongs-to-many%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91)

これを使用すれば簡単な多対多の場合は `has_many :through`の関連づけを使用しなくても良さそう。
早速モデルを作成してみる。

```terminal
$ rails g model assembly

$ rails g model part

$ rails g model assemblies_part
```

次にマイグレーションファイルを修正してmigrateする。

```ruby

class CreateAssemblies < ActiveRecord::Migration[6.0]
  def change
    create_table :assemblies do |t|
      t.string  :name
      t.timestamps
    end
  end
end
```

```ruby

class CreateParts < ActiveRecord::Migration[6.0]
  def change
    create_table :parts do |t|
      t.string :part_number
      t.timestamps
    end
  end
end
```

```ruby

class CreateAssembliesParts < ActiveRecord::Migration[6.0]
  def change
    create_table :assemblies_parts do |t|
      t.belongs_to :assembly
      t.belongs_to :part
      t.timestamps
    end
  end
end
```

```terminal
 $ rails db:migrate
```

テーブルが作成できたのでレコードをいくつか用意する。

```
rails c
> Assembly.create(name: 'assembly1')
> Assembly.create(name: 'assembly2')
> Assembly.create(name: 'assembly3')

> Part.create(part_number: '1')
> Part.create(part_number: '2')
> Part.create(part_number: '3')

> AssembliesPart.create(assembly_id: 1, part_id: 1)
> AssembliesPart.create(assembly_id: 1, part_id: 2)
> AssembliesPart.create(assembly_id: 1, part_id: 3)
> AssembliesPart.create(assembly_id: 2, part_id: 1)
> AssembliesPart.create(assembly_id: 3, part_id: 1)
```

最後にモデルにアソシエーションを記述

```ruby
class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end
```

```ruby

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end
```

コンソールで実際に取得できるか確認する。

```terminal
rails c
> Assembly.first.parts
  Assembly Load (2.0ms)  SELECT `assemblies`.* FROM `assemblies` ORDER BY `assemblies`.`id` ASC LIMIT 1
  Part Load (1.4ms)  SELECT `parts`.* FROM `parts` INNER JOIN `assemblies_parts` ON `parts`.`id` = `assemblies_parts`.`part_id` WHERE `assemblies_parts`.`assembly_id` = 1
=> [#<Part:0x00007fe6bd533e90 id: 1, part_number: "1", created_at: Mon, 15 Mar 2021 16:51:06 JST +09:00, updated_at: Mon, 15 Mar 2021 16:51:06 JST +09:00>,
 #<Part:0x00007fe6bd533dc8 id: 2, part_number: "2", created_at: Mon, 15 Mar 2021 16:51:08 JST +09:00, updated_at: Mon, 15 Mar 2021 16:51:08 JST +09:00>,
 #<Part:0x00007fe6bd533d00 id: 3, part_number: "3", created_at: Mon, 15 Mar 2021 16:51:10 JST +09:00, updated_at: Mon, 15 Mar 2021 16:51:10 JST +09:00>]

> Part.first.assemblies
  Part Load (0.2ms)  SELECT `parts`.* FROM `parts` ORDER BY `parts`.`id` ASC LIMIT 1
  Assembly Load (0.2ms)  SELECT `assemblies`.* FROM `assemblies` INNER JOIN `assemblies_parts` ON `assemblies`.`id` = `assemblies_parts`.`assembly_id` WHERE `assemblies_parts`.`part_id` = 1
=> [#<Assembly:0x00007fe6bd0d7658 id: 1, name: "assembly1", created_at: Mon, 15 Mar 2021 16:50:37 JST +09:00, updated_at: Mon, 15 Mar 2021 16:50:37 JST +09:00>,
 #<Assembly:0x00007fe6bd0be130 id: 2, name: "assembly2", created_at: Mon, 15 Mar 2021 16:53:05 JST +09:00, updated_at: Mon, 15 Mar 2021 16:53:05 JST +09:00>,
 #<Assembly:0x00007fe6bd0bded8 id: 3, name: "assembly3", created_at: Mon, 15 Mar 2021 16:53:07 JST +09:00, updated_at: Mon, 15 Mar 2021 16:53:07 JST +09:00>]
```

すごい。 `has_many :through` の記述なしでできた。
両者の使い分けだがRailsガイドには以下のように記述されている。

>どちらを使うかについてですが、経験上、リレーションシップのモデルそれ自体を独立したエンティティとして扱いたい(両モデルの関係そのものについて処理を行いたい)のであれば、中間にjoinモデルを使うhas_many :throughリレーションシップを選ぶのが最もシンプルです。リレーションシップのモデルで何か特別なことをする必要がまったくないのであれば、joinモデルの不要なhas_and_belongs_to_manyリレーションシップを使うのがシンプルです(ただし、こちらの場合はjoinモデルが不要な代わりに、専用のjoinテーブルを別途データベースに作成しておく必要がありますので、お忘れなきよう)。

中間テーブルをただ多対多を表現するためだけに使用するなら `has_and_belongs_to_many`
中間テーブルに外部キー以外のカラムがあり、そのモデル自体を独立した状態で処理をする場合は `has_many :through`

よくある投稿のいいね機能とかも `has_and_belongs_to_many`でいいのではないだろうか？

```ruby
class User < ApplicationRecord
  has_and_belongs_to_many :posts
end
```

```ruby
class Post < ApplicationRecord
  has_and_belongs_to_many :users
end
```

```ruby
class UsersPost < ApplicationRecord
  validates_uniqueness_of :user_id, scope: :post_id
end
```

例えば
`User.first.posts`でユーザーがいいねしている投稿を取得できる
また
`Post.first.users`で投稿にいいねしているユーザーの一覧を取得できる。
中間テーブルには`user_idとpost_idが一致する組み合わせはユニーク`であるというバリデーションをかけている。


中間テーブルに外部キー以外の別のカラムが存在する場合の例も紹介しようと思ったがパッと思いつかなっかったのでここまで。
また思いついたら追記します。