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>)
    

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.generationsObject

Returns a hash of all nodes grouped by their level in the tree structure.

Class.generations # => { 0=> [root1, root2], 1=> [root1child1, root1child2, root2child1, root2child2] }



135
136
137
# File 'lib/acts_as_tree.rb', line 135

def self.generations
  all.group_by{ |node| node.tree_level }
end

Instance Method Details

#acts_as_tree(options = {}) ⇒ Object

Configuration options are:

  • primary_key - specifies the column name for relations

    (default: +id+)
    
  • 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.
    


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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/acts_as_tree.rb', line 66

def acts_as_tree(options = {})
  configuration = {
    primary_key:   "id",
    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

  if ActiveRecord::VERSION::MAJOR >= 5
    belongs_to :parent,
      class_name:    name,
      primary_key:   configuration[:primary_key],
      foreign_key:   configuration[:foreign_key],
      counter_cache: configuration[:counter_cache],
      touch:         configuration[:touch],
      inverse_of:    :children,
      optional:      true
  else
    belongs_to :parent,
      class_name:    name,
      primary_key:   configuration[:primary_key],
      foreign_key:   configuration[:foreign_key],
      counter_cache: configuration[:counter_cache],
      touch:         configuration[:touch],
      inverse_of:    :children
  end

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

  include ActsAsTree::InstanceMethods

  define_singleton_method :default_tree_order do
    order(configuration[:order])
  end

  define_singleton_method :root do
    self.roots.first
  end

  define_singleton_method :roots do
    where(configuration[:foreign_key] => nil).default_tree_order
  end

  # Returns a hash of all nodes grouped by their level in the tree structure.
  #
  # Class.generations # => { 0=> [root1, root2], 1=> [root1child1, root1child2, root2child1, root2child2] }
  def self.generations
    all.group_by{ |node| node.tree_level }
  end


  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 efficient 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("\#{connection.quote_column_name('#{configuration[:primary_key]}')} NOT IN (\#{internal_ids.to_sql})").default_tree_order
      end
    EOV
  end
end