Rails: как отключить обратный вызов before_destroy, когда он разрушается из-за того, что родительский объект уничтожается (: зависимый =>: уничтожить) - PullRequest
19 голосов
/ 25 января 2012

У меня есть два класса: родитель и ребенок с

ребенок:

belongs_to :parent

и

родитель

has_many :children, :dependent => :destroy

Проблема в том, чтоЯ хочу проверить, что всегда присутствует хотя бы один дочерний элемент, поэтому у меня есть метод before_destroy в Child, который прерывает уничтожение, если это единственный дочерний элемент, принадлежащий его родителю.

И, если я хочу уничтожитьродительский объект будет вызывать обратный вызов before_destroy для каждого дочернего элемента, но когда есть один дочерний элемент, он отменяет уничтожение, поэтому родительский элемент никогда не будет уничтожен.

Как я могу сказать дочернему элементу вызывать before_destroyобратный вызов, только если он не уничтожен из-за своего родителя?

Спасибо!

Ответы [ 5 ]

11 голосов
/ 25 января 2012
has_many :childs, :dependent => :delete_all

Это удалит все дочерние элементы без , выполняющих какие-либо хуки.

Документацию можно найти по адресу: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many

5 голосов
/ 10 августа 2016

В 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 для родителя, он не будет.

4 голосов
/ 16 апреля 2014

ответ карпа выше будет работать, если вы установите prepend в true в методе before_destroy. Попробуйте это:

Ребенок:

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

Мы должны были сделать это, потому что мы используем Paranoia, и dependent: delete_all будет их удалять, а не удалять. Моя интуиция говорит мне, что есть лучший способ сделать это, но это не очевидно, и это делает работу.

1 голос
/ 28 апреля 2014

Принятый ответ не решает исходную проблему. Хосе хотел 2 вещи:

1) Чтобы у родителя всегда был хотя бы один ребенок

и

2) Чтобы можно было удалить всех детей при удалении Родителя

Вам не нужны никакие обратные вызовы before_destroy, чтобы предотвратить удаление ребенка.

Я написал подробное сообщение в блоге, описывающее решение , но здесь я также расскажу об основах.

Решение включает в себя различные ингредиенты: использование проверки присутствия и вложенных атрибутов в модели 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 . По сути, у вас будет одна форма с полями как для родителей, так и для детей. Любые обновления для Location, а также для Children, включая удаление потомков, будут обрабатываться действием update в родительском контроллере.

Способ удаления дочернего элемента с помощью вложенных форм заключается в передаче ключа с именем _destroy со значением, равным true. Опция allow_destroy: true, которую мы установили в родительской модели, позволяет это сделать. Документация для вложенных атрибутов Active Record охватывает это, но вот быстрый пример, который показывает, как вы удалите ребенка, чей id равен 2 из его родителя:

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

Обратите внимание, что вам не нужно делать это самостоятельно в родительском контроллере, если вы используете вложенные формы, как в Railscasts # 196. Rails позаботится об этом за вас.

С проверкой присутствия в родительской модели Rails автоматически предотвратит удаление последнего потомка.

Я думаю, что в то время, когда Хосе разместил свой вопрос, проверка присутствия не работала так, как предполагалось. Это не было исправлено до июля 2012 года с этим запросом на получение , но это было почти 2 года назад. Наблюдение за тем, как dbortz опубликовал свое устаревшее решение 12 дней назад, заставило меня осознать, что по-прежнему существует путаница в этом вопросе, поэтому я хотел опубликовать правильное решение.

Альтернативное решение, которое не использует вложенные формы, см. В моем блоге: http://www.moncefbelyamani.com/rails-prevent-the-destruction-of-child-object-when-parent-requires-its-presence/

1 голос
/ 25 января 2012

Возможно, есть способ сделать это менее хакерским способом, но вот (непроверенная!) Идея: добавить attr_accessor :destroyed_by_parent в Child и отредактировать дочерний фильтр before_destroy, чтобы разрешить уничтожение, когда оно true.

Добавить фильтр 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, этоможет работатьЕсли он создает экземпляры для детей по отдельности, он не будет работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...