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] }



131
132
133
# File 'lib/acts_as_tree.rb', line 131

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

  belongs_to_opts = {
    class_name:    name,
    primary_key:   configuration[:primary_key],
    foreign_key:   configuration[:foreign_key],
    counter_cache: configuration[:counter_cache],
    touch:         configuration[:touch],
    inverse_of:    :children
  }

  belongs_to_opts[:optional] = true if ActiveRecord::VERSION::MAJOR >= 5

  belongs_to :parent, belongs_to_opts

  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

  class_eval <<-EOV
    include ActsAsTree::InstanceMethods

    def self.default_tree_order
      order_option = #{configuration[:order].inspect}
      order(order_option)
    end

    def self.root
      self.roots.first
    end

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

  # 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