before_edestroy用于销毁依赖记录的模型



我遇到这样的情况:

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线程。

最新更新