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 の方に付け替えられて無事生き残ることができました。良かったです。