我在Rails 3.2.16中工作。在我的应用中,一个帐户有很多用户。帐户必须始终拥有管理员用户,因此您无法销毁这些用户。这样可以解决这个问题:
class Account < ActiveRecord::Base
has_many :users, :dependent => :destroy
end
class User < ActiveRecord::Base
before_destroy :check_if_admin
def check_if_admin
false if self.is_admin
end
end
但是,当您销毁整个帐户时,管理员也应该销毁。相反,当我从控制器调用@account.destroy
时,User#before_delete
回调会阻止管理员用户被销毁。
我知道我可以致电@account.delete
来跳过回调,但我的理解是:dependent => :destroy
本身就是一个回调,因此只删除帐户,而不是用户。
回调中是否有办法知道我来自哪里,例如
def check_if_admin
return if [I'm doing an Account dependent delete]
false if self.is_admin
end
或者我必须在delete
帐户之前手动destroy
用户?
答案 0 :(得分:2)
在this answer的帮助下,我找到了这个解决方案。这个想法是在摧毁父母时暂时关闭特定的子回调。请注意,我必须添加:prepend => :true
选项才能将我的自定义回调重新添加到链的前面。
class Account < ActiveRecord::Base
before_destroy :disable_user_check_if_admin
before_destroy :enable_user_check_if_admin
has_many :users, :dependent => :destroy
def disable_user_check_if_admin
User.skip_callback(:destroy, :before, :check_if_admin)
end
def enable_user_check_if_admin
User.set_callback(:destroy, :before, :check_if_admin), :prepend => :true
end
end
class User < ActiveRecord::Base
before_destroy :check_if_admin
has_many :contacts, :dependent => :restrict
def check_if_admin
false if self.is_admin
end
end
没有:prepend => :true
,我遇到了麻烦,因为我的用户模型也是has_many :contacts, :dependent => :restrict
。问题是,skip_callback
实际上删除了回调,并且set_callback
在回调链的末尾重新添加了回调 。使用:prepend => :true
,我能够在链的前面插入我的自定义before_destroy :check_if_admin
回调。请参阅文档和源代码here。
替代解决方案(未使用)
当我在回调序列时,我尝试了一种不同的解决方案,使回调保持不变。借用this answer,我在帐户上使用了一个访问者来检查它何时被删除:
class Account < ActiveRecord::Base
before_destroy :disable_user_check_if_admin
before_destroy :enable_user_check_if_admin
has_many :users, :dependent => :destroy
attr_accessor :destroying
def disable_user_check_if_admin
self.destroying = true
end
def enable_user_check_if_admin
self.destroying = false
end
end
class User < ActiveRecord::Base
before_destroy :check_if_admin
has_many :contacts, :dependent => :restrict
def check_if_admin
return if self.account.destroying
false if self.is_admin
end
end
这确实有效,但设置和检查这样的标志并没有“气味”,所以我使用:prepend => :true
回到了skip / set回调。