has_many関連作成時に使えるようになるメソッド

# has_many
モデルにこれを指定することで関連先のモデルのオブジェクトのCRUD処理を簡単にすることができる。その時に使うメソッドの挙動についてまとめる。

# colletion
関連先のオブジェクト(objects)を返す。

# collection << (object, …)
collectionにオブジェクトを１つ以上追加でき、自動で外部キーが設定される。この操作は、親のオブジェクトが新しいレコードでない限り、**即座**にSQL　update操作が発火され関連先オブジェクトのvalidationやcallbackも走る。
https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-3C-3C

# collection.delete(object, …)
基本の動作としては、objectの外部キーをNULLに設定する。加えて、`dependent: :destroy`が指定されている場合、ActiveRecordを通して`destroy`が実行され削除される。故に、callbackなどが走る。`dependent: :delete_all`が指定されていた場合、関連先のオブジェクトがデータベースから削除される。callbackなどは走らない。

また、`:through`オプションが使用されていた場合、デフォルトで`dependent: :delete_all`を指定した場合を同じ挙動をする。これを変更するためには、`dependent: :destroy`や`dependent: :nullify`指定すれば良い。

https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-delete

# collection.destroy(object, …)
`collection.delete`と違い、ActiveRecordを通して`destroy`を実行し削除する。よって、callbackなどが走る。これは、関連先オブジェクトの`:dependent`オプションの指定にかかわらず実行される。

# collection=objects
collectionが持つ既存のオブジェクトを削除し指定したobjectsに置き換える。

# collection_singular_ids
関連先オブジェクトのidを配列として返す。

# collection_singular_ids=ids
指定されたidsでcollectionの内容を置き換える。元からあったオブジェクトは削除される。 

## 具体的な動作
投稿用のテーブル

```ruby:post.rb
class Post < ApplicationRecord
  has_many :tags, class_name: 'Tagging'
  has_many :programming_languages, through: :tags

  validates :title, :description, presence: true
end
```

プログラミング言語を保持するテーブル

```ruby:programming_language.rb
class ProgrammingLanguage < ApplicationRecord
  has_many :tags, class_name: 'Tagging'
  has_many :posts, through: :tags

  validates :name, presence: true
  validates :name, uniqueness: true
end
```

上記２つの中間テーブル

```ruby:tagging.rb
class Tagging < ApplicationRecord
  belongs_to :post
  belongs_to :programming_language

  validates :post_id, uniqueness: { scope: :programming_language_id }
end
```

```ruby:example.rb
post.programming_languages_ids
# => [12, 13, 14]
post.programming_languages_ids = [1, 2]

# ProgrammingLanguage Load
# SELECT "programming_languages".* FROM "programming_languages" WHERE "programming_languages"."id" IN ($1, $2)  [["id", 1], ["id", 2]]
# ProgrammingLanguage Load 
# SELECT "programming_languages".* FROM "programming_languages" INNER JOIN "taggings" ON "programming_languages"."id" = "taggings"."programming_language_id" WHERE "taggings"."post_id" = $1  [["post_id", 2]]

# TRANSACTION
# Tagging Destroy
# DELETE FROM "taggings" WHERE "taggings"."post_id" = $1 AND "taggings"."programming_language_id" IN ($2, $3, $4)  [["post_id", 2], ["programming_language_id", 12], ["programming_language_id", 13], ["programming_language_id", 14]]

# Tagging Exists?
# SELECT 1 AS one FROM "taggings" WHERE "taggings"."post_id" = $1 AND "taggings"."programming_language_id" = $2 LIMIT $3  [["post_id", 2], ["programming_language_id", 1], ["LIMIT", 1]]

# Tagging Create
# INSERT INTO "taggings" ("post_id", "programming_language_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["post_id", 2], ["programming_language_id", 1], ["created_at", "2021-10-19 10:47:48.071965"], ["updated_at", "2021-10-19 10:47:48.071965"]]

# Tagging Exists?
# SELECT 1 AS one FROM "taggings" WHERE "taggings"."post_id" = $1 AND "taggings"."programming_language_id" = $2 LIMIT $3  [["post_id", 2], ["programming_language_id", 2], ["LIMIT", 1]]

# Tagging Create
# INSERT INTO "taggings" ("post_id", "programming_language_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["post_id", 2], ["programming_language_id", 2], ["created_at", "2021-10-19 10:47:48.081659"], ["updated_at", "2021-10-19 10:47:48.081659"]]
```

SQLを読み解いていく。

1. 新たなcollectionとして追加するids(1, 2)を持つProgrammingLanguageレコードと現在collectionとして持つProgrammingLanguageレコードを取得している。

1. PostとProgrammingLanguageを繋ぐ中間テーブルのレコードを削除する。

1. `tag.post_id = 2`, `tag.programming_language_id = 1`のレコードがtagging テーブルに存在するか確認

1. 存在しない場合、レコードをインサート

1. `tag.programming_language_id = 2`の場合も同様の操作を繰り返す

# collection.clear
collectionからオブジェクトを削除する。削除のされ方は`dependent`オプションによる。
`:through`オプションが指定されていた場合、**Join models**(これは中間テーブルのことなのかイマイチ分かってない)は、callbackが走らずダイレクトにDBから削除される。

# まとめ
collectionとしてオブジェクトを操作する場合にcallbackなどが実行されるのかされないのか、DBへの操作まで含むのか、それともただアプリケーション上にメモリとして保存するだけなのかを意識するのが重要。


# 参考記事
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many

https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html