module ComfortableMexicanSofa::ActsAsTree def self.included(base) base.extend(ClassMethods) end module ClassMethods def acts_as_tree(options = {}) configuration = { :foreign_key => 'parent_id', :order => nil, :counter_cache => nil, :dependent => :destroy, :touch => false } configuration.update(options) if options.is_a?(Hash) belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache], :touch => configuration[:touch] has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] class_eval <<-EOV include ComfortableMexicanSofa::ActsAsTree::InstanceMethods scope :roots, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}} def self.root roots.first end validates_each "#{configuration[:foreign_key]}" do |record, attr, value| if value if record.id == value record.errors.add attr, "cannot be it's own id" elsif record.descendants.map {|c| c.id}.include?(value) record.errors.add attr, "cannot be a descendant's id" end end end EOV end end module InstanceMethods # Returns list of ancestors, starting from parent until root. # # subchild1.ancestors # => [child1, root] def ancestors node, nodes = self, [] nodes << node = node.parent while node.parent nodes end # Returns all children and children of children def descendants nodes = [] self.children.each do |c| nodes << c nodes << c.descendants end nodes.flatten end # Returns the root node of the tree. def root node = self node = node.parent while node.parent node end # Checks if this node is a root def root? !self.parent end # Returns all siblings of the current node. # # subchild1.siblings # => [subchild2] def siblings self_and_siblings - [self] end # Returns all siblings and a reference to the current node. # # subchild1.self_and_siblings # => [subchild1, subchild2] def self_and_siblings parent ? parent.children : self.class.roots end end end ActiveRecord::Base.send :include, ComfortableMexicanSofa::ActsAsTree