Удалить осиротевшего родителя

#ruby-on-rails #ruby #activerecord

#ruby-on-rails #ruby #activerecord

Вопрос:

У меня такие отношения:

 Parent
  has_many :children

Child
  belongs_to :parent
  

Что я хочу сделать, так это удалить родительский элемент, если дочерних элементов больше не осталось. Итак, для этого у меня есть:

 Child
    before_destroy :destroy_orphaned_parent

    def destroy_orphaned_parent
      parent.children.each do |c|
        return if c != self
      end
      parent.destroy
    end
  

Это работает нормально, однако я также хочу каскадировать удаление родительского элемента дочернему. Например. Обычно я бы сделал:

 Parent
  has_many :children, :dependent => :destroy
  

Это приводит к сбою сервера WEBrick при его тестировании. Я предполагаю, что это связано с бесконечным циклом последнего дочернего элемента, удаляющего родительский элемент, удаляющего дочерний элемент и т.д.

Я начинаю думать, что есть лучший способ сделать это? У кого-нибудь есть идеи? Есть ли способ предотвратить эту рекурсию?

Комментарии:

1. Подумал, что я бы добавил, что это parent.destroy if parent.children.empty? более лаконично, чем тот цикл с ранним завершением в destroy_orphaned_parent . Кроме того, если ни родителю, ни дочерним элементам не нужно выполнять какую-либо очистку, вы можете использовать delete and delete_all вместо destroy и destroy_all , чтобы пропустить обратные вызовы.

Ответ №1:

Я добился этого следующим образом:

   before_destroy :find_parent
  after_destroy :destroy_orphaned_parent

  def find_parent
    @parent = self.parent
  end

  def destroy_orphaned_parent
    if @parent.children.length == 0
      @parent.destroy
    end  
  end
  

Согласно предложению Анвара, это также может быть выполнено с помощью around обратного вызова следующим образом:

   around_destroy :destroy_orphaned_parent

  def destroy_orphaned_parent
    parent = self.parent
    yield # executes a DELETE database statement
    if parent.children.length == 0
      parent.destroy
    end  
  end
  

Я не тестировал вышеупомянутое решение, поэтому не стесняйтесь обновлять его, если это необходимо.

Комментарии:

1. Вместо использования обратного вызова до и после также можно использовать обратный вызов around

Ответ №2:

Некоторые идеи:

  • Вы могли бы удалить осиротевших родителей в after_destroy (найдите их, используя инструкцию, подобную той, что на http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/a3f12d578f5a2619 )

  • Вы могли бы установить некоторую переменную экземпляра в, before_destroy содержащую идентификатор родителя, затем выполнить поиск на основе этого идентификатора в after_destroy обратном вызове и решить, удалять ли родительский элемент там, основываясь на подсчете дочерних элементов

Комментарии:

1. еще не тестировал это, но я думаю, что ключ в том, чтобы переместить логику в after_destroy, а не в before_destroy, как у меня было в моем первоначальном сообщении

Ответ №3:

Используйте after_destroy обратный вызов.

 after_destroy :release_parent

def release_parent
  if parent.children.count.zero?
    parent.destroy
  end
end
  

Использование Rails 3.2.15

Ответ №4:

Вы можете выполнить это с помощью around_destroy обратного вызова

 around_destroy :destroy_orphaned_parent

def destroy_orphaned_parent
  @parent = self.parent
  yield
  @parent.destroy if @parent.children.length == 0
end
  

Ответ №5:

 after_destroy :destroy_parent, if: parent_is_orphan?

private

def parent_is_orphan?
 parent.children.count.zero?
end

def destroy_parent
 parent.destroy
end