Проверка правильности перемещения узла во вложенном наборе - PullRequest
2 голосов
/ 04 июня 2009

Я создаю модель категории и использую плагин awesome_nested_set (замена для acts_as_nested_set) для работы с иерархией. При awesome_nested_set объекты создаются, затем сохраняются, а затем помещаются в набор. Кроме того, lft, rgt и parent_id являются attr_protected, поэтому они не могут быть записаны напрямую.

Я сталкиваюсь с двумя ситуациями, когда помещаю узел в набор, который я хочу иметь возможность перехватить, чтобы уведомить пользователя (может быть, еще кое-что, о чем я еще не думал):

  1. Попытка размещения узла в качестве собственного дочернего элемента (self.id == self.parent_id)
  2. Узел пытается быть перемещен ниже своего потомка (self.descendants.include? self.parent_id == true)

В обоих случаях перемещение завершится неудачно, но awesome_nested_set вызовет только исключение ActiveRecord::ActiveRecordError с сообщением, которое не настолько описательно, как я хотел бы дать пользователю.

awesome_nested_set имеет несколько методов перемещения узлов, которые все вызывают move_to(target, position) (где position - это один из :root, :child, :left или :right, а target - это связанный узел для всех position с, но :root). Метод запускает обратный вызов before_move, но не предоставляет способа, который я вижу, чтобы проверить движение до того, как оно произойдет. Чтобы проверить ход, мне понадобится доступ к цели и позиции, которые обратный вызов не получает.

Кто-нибудь знает либо о способе проверки хода в awesome_nested_set (либо путем передачи цели и позиции в обратный вызов before_move другого метода), либо о другом плагине вложенных множеств, который позволит мне проверки? Я бы предпочел не раскошелиться или написать свой собственный плагин.

1 Ответ

3 голосов
/ 29 июня 2009

Вот решение, которое я придумал:

class Category < ActiveRecord::Base
  acts_as_nested_set :dependent => :destroy

  #=== Nested set methods ===

  def save_with_place_in_set(parent_id = nil)
    Category.transaction do
      return false if !save_without_place_in_set
      raise ActiveRecord::Rollback if !validate_move parent_id

      place_in_nested_set parent_id
      return true
    end

    return false
  end

  alias_method_chain :save, :place_in_set

  def validate_move(parent_id)
    raise ActiveRecord::RecordNotSaved, "record must be saved before moved into the nested set" if new_record?
    return true if parent_id.nil?

    parent_id = parent_id.to_i

    if self.id == parent_id
      @error = :cannot_be_child_of_self
    elsif !Category.all.map(&:id).include?(parent_id)
      @error = :given_parent_is_invalid
    elsif descendants.map(&:id).include? parent_id
      @error = :cannot_be_child_of_descendant
    end

    errors.add(:parent_id, @error) if @error
    return @error.nil?
  end

  def place_in_nested_set(parent_id)
    if parent_id.nil? || parent_id.blank?
      move_to_root
    else
      move_to_child_of parent_id
    end
    return true
  end
end

Теперь в контроллере мне просто нужно сказать @category.save(parent_id), где parent_id - это nil или идентификатор родителя, а проверка, размещение узла и сохранение обрабатываются в модели.

...