11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

株式会社ラグザイアAdvent Calendar 2023

Day 5

`dependent: :destroy`のhas_one関連レコードbuild時の奇妙な挙動

Last updated at Posted at 2023-12-04

ある時、has_oneの関連レコードがすでに紐づいている状態で、build_association_nameで新たなレコードを作成しsaveをしても、特にエラーなどが起きずレコードが置き換わっていることに気づきました。
この挙動が奇妙だったため何が起きているのか調査をしてみることにしました。

has_oneのbuild_associationでDELETEが実行されていた

rails7.1.2で以下のようなモデルを使って検証をしてみます。
ポイントはhas_oneに対してdependent: :destroyのオプションを与えている点です。

class Supplier < ApplicationRecord
  has_one :account, dependent: :destroy
end

class Account < ApplicationRecord
  belongs_to :supplier
end

Supplierのhas_oneの関連レコードであるAccountを永続化して紐付けた状態にしておきます。

irb(main):005> sup = Supplier.create!(name: 'hoge')
=> #<Supplier:0x0000000108691aa0 id: 2, name: "hoge", created_at: Sat, 25 Nov 2023 03:28:05.312517000 UTC +00:00, updated_at: Sat, 25 Nov 2023 03:28:05.312517000 UTC +00:00>

irb(main):006> sup.create_account!(account_number: '1')
=> #<Account:0x0000000109d6fb78 id: 2, supplier_id: 2, account_number: "1", created_at: Sat, 25 Nov 2023 03:29:36.497616000 UTC +00:00, updated_at: Sat, 25 Nov 2023 03:29:36.497616000 UTC +00:00>

この状態でbuild_accountを実行すると、以下のようにbuildの時点でDELETEのSQLが発行され既存のAccountのレコードを削除してしまいます。
どうやらbuild_association_nameメソッドが実行された際に、紐づく既存レコードがあれば暗黙的に削除されるようです。

irb(main):008> sup.build_account(account_number: '2')
  TRANSACTION (0.1ms)  begin transaction
  Account Destroy (0.9ms)  DELETE FROM "accounts" WHERE "accounts"."id" = ?  [["id", 2]]
  TRANSACTION (0.5ms)  commit transaction
=> #<Account:0x0000000109c8f618 id: nil, supplier_id: 2, account_number: "2", created_at: nil, updated_at: nil>

もしbuild後のsaveの処理の際に例外が発生すれば、ただ既存レコードが削除されただけとなってしまうのは少し恐ろしいなと感じます...。
ちなみに、dependent: :destroyが付与されていないと、buildしようとした時点で以下のように例外が起きます。

irb(main):016> sup.build_account(account_number: 1)
/Users/username/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/activerecord-7.1.2/lib/active_record/associations/has_one_association.rb:110:in `remove_target!': Failed to remove the existing associated account. The record failed to save after its foreign key was set to nil. (ActiveRecord::RecordNotSaved)

まとめ

既存のレコードが紐づいた状態でdependent: :destroyが付与されたhas_one関連レコードをbuild_associationする際は、暗黙的に既存レコードが削除されてしまうことに注意しておきましょう。
buildが既存レコードに影響を及ぼすとは考えていなかったため、この挙動を知った時は驚きました。
もしこのような挙動となっている理由をご存知の方がいたら教えていただきたいです。

ちなみに以下の記事で紹介されているように、create_associationメソッドの挙動に関しても一悶着あったようです。
投稿者の方がおっしゃるように、全員が幸せになるような挙動を作るのはなかなか難しいのかもしれません・・・。

11
0
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
11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?