Rails ActiveRecord 3.2:如何在子模型中跳过before_delete回调?

时间:2014-03-27 01:30:28

标签: ruby-on-rails-3 activerecord

我在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用户?

1 个答案:

答案 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回调。