我遇到这样的情况:
class Shop < ActiveRecord::Base
has_many :services, dependent: :destroy
end
class Service < ActiveRecord::Base
has_many :model_weeks, dependent: :destroy
end
class ModelWeek < ActiveRecord::Base
before_destroy :prevent_destroy, if: :default?
private
def prevent_destroy
false
end
end
当我试图销毁商店时,我会得到ActiveRecord::RecordNotDestroyed: Failed to destroy the record
,因为它首先开始销毁关联的记录,并且被ModelWeek中的回调阻止。
销毁商店时,我可以很容易地取消ModelWeek的默认设置,只要我能抓住它。Shop
模型中的before_destroy
在引发上述异常之前不会被触发。
那么,有没有办法在Shop模型中捕捉到这一点,或者如果没有,有没有可能在ModelWeek中"知道"销毁是由父级触发的?我研究了解析调用者,但它没有提供任何有用的东西,而且无论如何都会很混乱。。。
我用一个有趣的破解方法解决了这个问题,你只需要把before_destroy
线关联线之前并且它将在删除关联
before_destroy
既然rails有从相关子级到父级的破坏链,这真的很有意义,对吧?为了方便起见,我们可以在ModelWeek中覆盖destroy方法,如下所示:
class ModelWeek < ActiveRecord::Base
# before_destroy :prevent_destroy, if: :default?
def destroy
unless default?
super
end
end
end
经过一些研究和测试,我得出了以下结论:
这是调用方法的顺序(do_before_destroy
是在before_destroy
回调中指定的任何方法):
- Shop.destroy
- Service.destroy
- ModelWeek.destroy
- ModelWeek.do_befor_edestroy
- 服务.do_销毁前
- 商店.do_销毁前
因此,我可以在父母(商店)的销毁方法中处理任何防止销毁孩子(ModelWeek)的事情:
# Shop
def destroy
# default is a scope
ModelWeek.default.where(service_id: self.services.pluck(:id)).each do |m|
m.unset_default
end
super
end
在那之后,没有任何东西能阻止孩子的毁灭,链条继续不受保护。
更新
还有更好、更干净的解决方案,无需覆盖父级的destroy
并执行任何查询:
class Shop < ActiveRecord::Base
has_many :services, dependent: :destroy, before_remove: :unset_default_week
private
def unset_default_week(service)
service.model_weeks.default.unset_default
end
end
现代解决方案是在带有prepend: true
选项的父模型中使用before_destroy
回调,使回调在dependent: :destroy
在关联中定义的销毁回调之前触发。
class Service < ActiveRecord::Base
has_many :model_weeks, dependent: :destroy
before_destroy :handle_model_weeks, prepend: true
private
def handle_model_weeks
# special handling here
end
end
另请参阅有关此的Rails线程。