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

我有两个class:父母和孩子

儿童:

belongs_to :parent 

 has_many :children, :dependent => :destroy 

问题是我想要检查是否总有至少一个子节点存在,所以我在Child中有一个before_destroy方法,如果它是属于其父节点的唯一子节点则中止destroy。

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

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

谢谢!

 has_many :childs, :dependent => :delete_all 

这将删除所有孩子而不运行任何钩子。

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

如果在before_destroy方法中将prepend设置为true,则上面的carp的答案将起作用。 试试这个:

儿童:

 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将硬删除而不是软删除它们。 我的直觉告诉我有一个更好的方法来做到这一点,但这并不明显,这就完成了工作。

在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 

这样,当直接在子check_destroy_allowed上调用destroy时会运行check_destroy_allowed回调,但是当你在父节点上调用destroy时,它不会。

可能有一种方法可以用不那么笨拙的方式来实现这一点,但这里有一个(未经测试的!)想法:将一个attr_accessor :destroyed_by_parent添加到Child并编辑Child的before_destroyfilter以允许在它为true时进行破坏。

将一个before_destroyfilter添加到Parent ,迭代其所有子项:

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

如果由:dependent => :destroy destroy触发的:dependent => :destroy在Parent对象的实例:dependent => :destroy上执行,则它可以工作。 如果它单独实例化子项,则不起作用。

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

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

2)删除父项时能够删除所有子项

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

我写了一篇详细的博客文章,描述了解决方案 ,但我也将在这里介绍基础知识。

该解决方案包括各种成分:在Parent模型中使用在线validation和嵌套属性,并确保删除子项的方法不会在子项上调用.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 

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

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

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

请注意,如果您使用Railscasts#196中的嵌套表单,则无需在父控制器中自行执行此操作。 Rails会为您解决这个问题。

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

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

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