Подтвердить что ты не робот

Rails: как отключить обратный вызов before_destroy, когда он уничтожается из-за родителя, уничтожается (: dependent =>: destroy)

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

Ребенок:

belongs_to :parent

и

Родитель

has_many :children, :dependent => :destroy

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

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

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

Спасибо!

4b9b3361

Ответ 2

Ответ carp выше будет работать, если вы установите preend 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 будет удалять жесткие, а не soft-delete. Моя кишка говорит мне, что есть лучший способ сделать это, но это не очевидно, и это выполняет свою работу.

Ответ 3

В 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

Вероятно, есть способ сделать это менее взломанным способом, но здесь (непроверенная!) идея: добавьте attr_accessor :destroyed_by_parent в Child и отредактируйте фильтр 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, выполняется на дочерних объектах родительского объекта, оно может работать. Если он создает экземпляры отдельно для детей, это не сработает.

Ответ 5

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

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

и

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

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

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

Решение включает в себя различные ингредиенты: использование проверки присутствия и вложенных атрибутов в родительской модели и обеспечение того, чтобы метод, который удаляет дочерний элемент, не вызывает .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. Опция allow_destroy: true, которую мы устанавливаем в родительской модели, позволяет это сделать. Документация для Active Record Nested Attributes описывает это, но здесь приведен краткий пример, показывающий, как удалить ребенка, чей 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/