Ruby
Rails

has_many などで親の付替えをして削除した時にハマったこと

Rails で has_many や has_one などのリレーションがあるモデルで、子が参照している親を付け替えるコードを書いていたらハマってしまったことがあったので備忘録を残しておきます。

やりたかったこと

  • 子レコードが参照している親レコードを別の親レコードへ変更する
  • 元々参照されていた親レコードは削除する

とあるユーザを削除する際に、所持しているデータを、別のユーザに所有権を渡して消さないようにする的なノリです。

現象

簡単に言うと、親レコードを削除した際に参照していた子レコードも一緒に削除されてしまいました。コードは以下のとおりです。

モデル

class User < ApplicationRecord
  has_many :books, dependent: :destroy
end

class Book < ApplicationRecord
  belongs_to :user
end

処理コード

事前にデータを用意しておきます。

r = User.new
r.name = 'zaru'
r.save

r = User.new
r.name = 'tofu'
r.save

r = Book.new
r.name = 'book 1'
r.user = User.first
r.save

二人の User がいて、一つの Book レコードがある状況です。ここから、Book を所持している User を削除します。削除する前に Book を別の User に託しておきます。

user1 = User.first
user2 = User.last

user1.books.each do |book|
  book.user = nil
  user2.books << book
end

user1.destroy

これを実行すると、 user2.books << book の時点でちゃんと UPDATE SQL が実行されて Book レコードの参照先は二人目の User になります。

ただし、その後の user1.destroy を実行すると Book も一緒に DELETE されてしまいます。これは user1 オブジェクトが Book を参照している状態のままなので、 destroy すると関連している Book レコードも一緒に削除しようと良い感じに働いてくれるからです。

対処法

オブジェクトの情報が古いだけなので慌てず騒がず reload して最新の情報に変更します。

user1 = User.first
user2 = User.last

user1.books.each do |book|
  book.user = nil
  user2.books << book
end

user1.reload # 追加
user1.destroy

これだけで user1 だけが削除されて、Book は user2 の方に付け替えられて無事生き残ることができました。良かったです。