Module: ActsAsTree::ClassMethods

Defined in:
lib/acts_as_tree.rb

Overview

Specify this acts_as extension if you want to model a tree structure by providing a parent association and a children association. This requires that you have a foreign key column, which by default is called parent_id.

class Category < ActiveRecord::Base
  include ActsAsTree

  acts_as_tree :order => "name"
end

Example:
root
 \_ child1
      \_ subchild1
      \_ subchild2

root      = Category.create("name" => "root")
child1    = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")

root.parent   # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1

In addition to the parent and children associations, the following instance methods are added to the class after calling acts_as_tree:

  • siblings - Returns all the children of the parent, excluding

    the current node (<tt>[subchild2]</tt> when called
    on <tt>subchild1</tt>)
    
  • self_and_siblings - Returns all the children of the parent,

    including the current node (<tt>[subchild1, subchild2]</tt>
    when called on <tt>subchild1</tt>)
    
  • ancestors - Returns all the ancestors of the current node

    (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>)
    
  • root - Returns the root of the current node (root

    when called on <tt>subchild2</tt>)
    

Instance Method Summary collapse

Instance Method Details

#acts_as_tree(options = {}) ⇒ Object

Configuration options are:

  • foreign_key - specifies the column name to use for tracking

    of the tree (default: +parent_id+)
    
  • order - makes it possible to sort the children according to

    this SQL snippet.
    
  • counter_cache - keeps a count in a children_count column

    if set to +true+ (default: +false+). Specify
    a custom column by passing a symbol or string.
    


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/acts_as_tree.rb', line 64

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)

  if configuration[:counter_cache] == true
    configuration[:counter_cache] = :children_count
  end

  belongs_to :parent, class_name:    name,
    foreign_key:   configuration[:foreign_key],
    counter_cache: configuration[:counter_cache],
    touch:         configuration[:touch],
    inverse_of:    :children

  if ActiveRecord::VERSION::MAJOR >= 4
    has_many :children, lambda { order configuration[:order] },
      class_name:  name,
      foreign_key: configuration[:foreign_key],
      dependent:   configuration[:dependent],
      inverse_of:  :parent
  else
    has_many :children, class_name:  name,
      foreign_key: configuration[:foreign_key],
      order:       configuration[:order],
      dependent:   configuration[:dependent],
      inverse_of:  :parent
  end

  class_eval <<-EOV
    include ActsAsTree::InstanceMethods

    def self.default_tree_order
      order_option = %Q{#{configuration.fetch :order, "nil"}}
      order(order_option)
    end

    def self.root
      self.roots.first
    end

    def self.roots
      where(:#{configuration[:foreign_key]} => nil).default_tree_order
    end
  EOV

  if configuration[:counter_cache]
    after_update :update_parents_counter_cache

    def children_counter_cache_column
      reflect_on_association(:parent).counter_cache_column
    end

    def leaves
      where(children_counter_cache_column => 0).default_tree_order
    end

  else
    # Fallback to less efficent ways to find leaves.
    class_eval <<-EOV
      def self.leaves
        internal_ids = select(:#{configuration[:foreign_key]}).where(arel_table[:#{configuration[:foreign_key]}].not_eq(nil))
        where("id NOT IN (\#{internal_ids.to_sql})").default_tree_order
      end
    EOV
  end
end