2
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-10-19

has_many

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

colletion

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

collection << (object, …)

collectionにオブジェクトを1つ以上追加でき、自動で外部キーが設定される。この操作は、親のオブジェクトが新しいレコードでない限り、即座に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: :destroydependent: :nullify指定すれば良い。

collection.destroy(object, …)

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

collection=objects

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

collection_singular_ids

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

collection_singular_ids=ids

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

具体的な動作

投稿用のテーブル

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

  validates :title, :description, presence: true
end

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

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

上記2つの中間テーブル

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

  validates :post_id, uniqueness: { scope: :programming_language_id }
end
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レコードを取得している。

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

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

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

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

collection.clear

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

まとめ

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

参考記事

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1