I am creating a Category model and using the awesome_nested_set plugin (a replacement for acts_as_nested_set) to deal with the hierarchy. With awesome_nested_set, objects are created, then saved, and then placed within the set. As well, lft, rgt and parent_id are attr_protected so they cannot be written to directly.
I am running into two situations when placing the node into the set that I want to be able to catch so that I notify the user (there might be more that I haven't thought of yet):
self.id == self.parent_id) self.descendants.include? self.parent_id == true)In both cases, the move will fail, but awesome_nested_set will only raise an ActiveRecord::ActiveRecordError exception, with a message that is not as descriptive as I'd like to be able to give the user.
awesome_nested_set has a number of node moving methods, which all call move_to(target, position) (where position is one of :root, :child, :left or :right and target is the related node for all positions but :root). The method fires a before_move callback, but doesn't provide a way that I can see to validate a move before it happens. In order to validate a move, I'd need access to the target and position, which the callback does not receive.
Does anyone know of either a way to validate a move in awesome_nested_set (either by having a way to pass target and position to the before_move callback of by another method), or another nested set plugin that will let me validate? I'd prefer not to fork or write my own plugin.
Here is the solution I came up with:
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
Now, in the controller, I just need to say @category.save(parent_id), where parent_id is nil or the ID of the parent, and the validation, node placement, and save is handled in the model.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With