Clone
acts_as_tree.rb
module ComfortableMexicanSofa::ActsAsTree
  
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def cms_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