Rails:如果因为父被销毁而被销毁时如何禁用before_destroy回调(:dependent =>:destroy)

时间:2012-01-25 09:45:36

标签: ruby-on-rails ruby-on-rails-3.1

我有两个课程:父母和孩子

子:

belongs_to :parent

has_many :children, :dependent => :destroy

问题是我想检查总是至少有一个孩子存在,所以我在Child中有一个before_destroy方法,如果它是属于其父级的唯一子项,则中止该销毁。

并且,如果我想要销毁父节点,它将在每个子节点上调用before_destroy回调,但是当有一个子节点时,它将中止销毁,因此父节点永远不会被销毁。

如果孩子因为父母而没有被销毁,我怎么能告诉孩子调用before_destroy回调呢?

谢谢!

5 个答案:

答案 0 :(得分:11)

has_many :childs, :dependent => :delete_all

这将删除所有子项,而不运行任何挂钩。

您可以在以下网址找到相关文档:http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many

答案 1 :(得分:5)

在Rails 4中,您可以执行以下操作:

class Parent < AR::Base
  has_many :children, dependent: :destroy
end

class Child < AR::Base
  belongs_to :parent

  before_destroy :check_destroy_allowed, unless: :destroyed_by_association

  private

  def check_destroy_allowed
    # some condition that returns true or falls
  end
end

这样,直接在某个孩子上调用destroy会运行check_destroy_allowed回调,但当您在父母上调用destroy时,则不会。{/ p>

答案 2 :(得分:4)

如果你在before_destroy方法中将prepend设置为true,那么上面的回答将会起作用。试试这个:

子:

belongs_to :parent
before_destroy :prevent_destroy
attr_accessor :destroyed_by_parent

...

private

def prevent_destroy
  if !destroyed_by_parent
    self.errors[:base] << "You may not delete this child."
    return false
  end
end

父:

has_many :children, :dependent => :destroy
before_destroy :set_destroyed_by_parent, prepend: true

...

private

def set_destroyed_by_parent
  children.each{ |child| child.destroyed_by_parent = true }
end

我们必须这样做,因为我们正在使用偏执狂,而dependent: delete_all会硬删除而不是软删除它们。我的直觉告诉我,这是一个更好的方法,但这并不明显,这就完成了工作。

答案 3 :(得分:1)

可能有一种方法可以用不那么笨拙的方式来实现这一点,但这是一个(未经测试的!)想法:向attr_accessor :destroyed_by_parent添加Child并编辑Child的before_destroy过滤器以允许在true时进行销毁{1}}。

将一个before_destroy过滤器添加到Parent,迭代其所有子项:

private

# custom before_destroy
def set_destroyed_by_parent
  self.children.each {|child| child.destroyed_by_parent = true }
end

如果由:dependent => :destroy触发的销毁在Parent对象的实例化子节点上执行,则它可以工作。如果它单独实例化子项,它将无法工作。

答案 4 :(得分:1)

接受的答案并不能解决原来的问题。何塞想要两件事:

1)确保父母至少有一个孩子

2)能够在删除父母时删除所有孩子

您不需要任何before_destroy回调来阻止删除孩子。

我写了detailed blog post describing the solution,但我也会在这里介绍基础知识。

解决方案包括各种成分:在Parent模型中使用状态验证和嵌套属性,并确保删除子项的方法不会在子项上调用.destroy,但是孩子是通过嵌套属性从父模型中删除。

在父模型中:

attr_accessible :children_attributes

has_many :children, dependent: :destroy
accepts_nested_attributes_for :children, allow_destroy: true
validates :children, presence: true

在儿童模型中:

belongs_to :parent

接下来,除最后一项外,允许删除子项的最简单方法是使用嵌套表单,如Railscasts #196所述。基本上,您将拥有一个包含父项和子项字段的表单。对位置以及子项的任何更新(包括删除子项)都将由父控制器中的update操作处理。

通过嵌套表单删除子项的方法是传入一个名为_destroy的键,其值为true。我们在Parent模型中设置的allow_destroy: true选项允许这样做。 Active Record Nested Attributes的文档涵盖了这一点,但这里有一个简单的示例,说明如何从其父级中删除id等于2的子项:

parent.children_attributes = { id: '2', _destroy: '1' }
parent.save

请注意,如果您使用Railscasts#196中的嵌套表单,则无需在父控制器中自行执行此操作。 Rails会为你处理它。

通过父模型中的状态验证,Rails将自动阻止删除最后一个子项。

我认为在Jose发布他的问题时,状态验证并没有按照预期的方式运行。这个pull request直到2012年7月才得到修复,但差不多是2年前。看到dbortz在12天前发布了他过时的解决方案让我意识到这个问题仍然存在混淆,所以我想确保发布正确的解决方案。

对于不使用嵌套表单的备用解决方案,请参阅我的博文:http://www.moncefbelyamani.com/rails-prevent-the-destruction-of-child-object-when-parent-requires-its-presence/