ユーザーがブログを書けるアプリケーションとかで、ユーザーが退会したら、関連するブログを削除する指定をすることは良くあると思います。
以下のような設定です。
class User < ActiveRecord::Base
has_many :blogs, dependent: :destroy
end
class Blog < ActiveRecord::Base
belongs_to :user
end
退会しても、ブログ情報はアーカイブして持っていたいと思った時とかは before_destroy を使って、削除前にブログのアーカイブ処理をやろうとか思っている時には、ちょっと注意が必要です。何かと言うと、before_destroy が実行される時には、すでに Blog 情報は削除されているといった問題です。
class User < ActiveRecord::Base
has_many :blogs, dependent: :destroy
before_destroy :ekusdesu_taosenai
private
def ekusdesu_taosenai
puts Blog.first.title
end
end
class Blog < ActiveRecord::Base
belongs_to :user
end
こんなコードを書いたとして、ユーザーを削除すると、エラーになります。
[1] pry(main)> u = User.new(name: "popo")
=> #<User:0x007fcd95dacf18 id: nil, name: "popo", created_at: nil, updated_at: nil>
[2] pry(main)> u.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "popo"], ["created_at", "2016-01-06 09:31:19.952772"], ["updated_at", "2016-01-06 09:31:19.952772"]]
(2.0ms) commit transaction
=> true
[3] pry(main)> b = Blog.new(title: "エクスデス強すぎ", user_id: u.id)
=> #<Blog:0x007fcd95c8d948 id: nil, title: "エクスデス強すぎ", created_at: nil, updated_at: nil, user_id: 18>
[4] pry(main)> b.save
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "blogs" ("title", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "エクスデス強すぎ"], ["user_id", 18], ["created_at", "2016-01-06 09:32:04.797796"], ["updated_at", "2016-01-06 09:32:04.797796"]]
(2.1ms) commit transaction
=> true
[5] pry(main)> u.destroy
(0.1ms) begin transaction
Blog Load (0.2ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = ? [["user_id", 18]]
SQL (1.1ms) DELETE FROM "blogs" WHERE "blogs"."id" = ? [["id", 12]]
Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" ORDER BY "blogs"."id" ASC LIMIT 1
(1.9ms) rollback transaction
NoMethodError: undefined method `title' for nil:NilClass
from /Users/namikata/work/before_destory/before_destory/app/models/user.rb:9:in `ekusdesu_taosenai'
この問題を回避するには before_destroy に対して prepend: true を指定します。prepend は英単語としては存在してなくて pre + append を組み合わせた造語らしいです。指定すると Blog を削除する前に、処理を行ってくれるようになります。
class User < ActiveRecord::Base
has_many :blogs, dependent: :destroy
before_destroy :ekusdesu_taosenai, prepend: true
private
def ekusdesu_taosenai
puts Blog.first.title
end
end
[1] pry(main)> u = User.new(name: "popo")
=> #<User:0x007fcd93432f48 id: nil, name: "popo", created_at: nil, updated_at: nil>
[2] pry(main)> u.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "popo"], ["created_at", "2016-01-06 09:29:40.860009"], ["updated_at", "2016-01-06 09:29:40.860009"]]
(2.0ms) commit transaction
=> true
[3] pry(main)> b = Blog.new(title: "エクスデス強すぎ", user_id: u.id)
=> #<Blog:0x007fcd95e97018 id: nil, title: "エクスデス強すぎ", created_at: nil, updated_at: nil, user_id: 17>
[4] pry(main)> b.save
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "blogs" ("title", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "エクスデス強すぎ"], ["user_id", 17], ["created_at", "2016-01-06 09:30:03.992078"], ["updated_at", "2016-01-06 09:30:03.992078"]]
(2.0ms) commit transaction
=> true
[5] pry(main)> u.destroy
(0.1ms) begin transaction
Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" ORDER BY "blogs"."id" ASC LIMIT 1
エクスデス強すぎ
Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = ? [["user_id", 17]]
SQL (0.4ms) DELETE FROM "blogs" WHERE "blogs"."id" = ? [["id", 11]]
SQL (0.2ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 17]]
(2.7ms) commit transaction
=> #<User:0x007fcd93432f48 id: 17, name: "popo", created_at: Wed, 06 Jan 2016 09:29:40 UTC +00:00, updated_at: Wed, 06 Jan 2016 09:29:40 UTC +00:00>