forked from collectiveidea/awesome_nested_set
-
Notifications
You must be signed in to change notification settings - Fork 0
/
awesome_nested_set.rb
137 lines (119 loc) · 5.76 KB
/
awesome_nested_set.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
137
require 'awesome_nested_set/columns'
require 'awesome_nested_set/model'
module CollectiveIdea #:nodoc:
module Acts #:nodoc:
module NestedSet #:nodoc:
# This acts provides Nested Set functionality. Nested Set is a smart way to implement
# an _ordered_ tree, with the added feature that you can select the children and all of their
# descendants with a single query. The drawback is that insertion or move need some complex
# sql queries. But everything is done here by this module!
#
# Nested sets are appropriate each time you want either an orderd tree (menus,
# commercial categories) or an efficient way of querying big trees (threaded posts).
#
# == API
#
# Methods names are aligned with acts_as_tree as much as possible to make replacment from one
# by another easier.
#
# item.children.create(:name => "child1")
#
# Configuration options are:
#
# * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
# * +:left_column+ - column name for left boundry data, default "lft"
# * +:right_column+ - column name for right boundry data, default "rgt"
# * +:depth_column+ - column name for the depth data, default "depth"
# * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
# (if it hasn't been already) and use that as the foreign key restriction. You
# can also pass an array to scope by multiple attributes.
# Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
# * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
# child objects are destroyed alongside this object by calling their destroy
# method. If set to :delete_all (default), all the child objects are deleted
# without calling their destroy method.
# * +:counter_cache+ adds a counter cache for the number of children.
# defaults to false.
# Example: <tt>acts_as_nested_set :counter_cache => :children_count</tt>
# * +:order_column+ on which column to do sorting, by default it is the left_column_name
# Example: <tt>acts_as_nested_set :order_column => :position</tt>
#
# See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and
# CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added
# to acts_as_nested_set models
def acts_as_nested_set(options = {})
acts_as_nested_set_parse_options! options
include Model
include Columns
extend Columns
acts_as_nested_set_relate_parent!
acts_as_nested_set_relate_children!
attr_accessor :skip_before_destroy
acts_as_nested_set_prevent_assignment_to_reserved_columns!
acts_as_nested_set_define_callbacks!
end
private
def acts_as_nested_set_define_callbacks!
# on creation, set automatically lft and rgt to the end of the tree
before_create :set_default_left_and_right
before_save :store_new_parent
after_save :move_to_new_parent, :set_depth!
before_destroy :destroy_descendants
define_model_callbacks :move
end
def acts_as_nested_set_relate_children!
has_many_children_options = {
:class_name => self.base_class.to_s,
:foreign_key => parent_column_name,
:inverse_of => (:parent unless acts_as_nested_set_options[:polymorphic]),
}
# Add callbacks, if they were supplied.. otherwise, we don't want them.
[:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback|
has_many_children_options.update(
ar_callback => acts_as_nested_set_options[ar_callback]
) if acts_as_nested_set_options[ar_callback]
end
has_many :children, -> { order(quoted_order_column_name) },
has_many_children_options
end
def acts_as_nested_set_relate_parent!
belongs_to :parent, :class_name => self.base_class.to_s,
:foreign_key => parent_column_name,
:counter_cache => acts_as_nested_set_options[:counter_cache],
:inverse_of => (:children unless acts_as_nested_set_options[:polymorphic]),
:polymorphic => acts_as_nested_set_options[:polymorphic],
:touch => acts_as_nested_set_options[:touch]
end
def acts_as_nested_set_default_options
{
:parent_column => 'parent_id',
:left_column => 'lft',
:right_column => 'rgt',
:depth_column => 'depth',
:dependent => :delete_all, # or :destroy
:polymorphic => false,
:counter_cache => false,
:touch => false
}.freeze
end
def acts_as_nested_set_parse_options!(options)
options = acts_as_nested_set_default_options.merge(options)
if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
options[:scope] = "#{options[:scope]}_id".intern
end
class_attribute :acts_as_nested_set_options
self.acts_as_nested_set_options = options
end
def acts_as_nested_set_prevent_assignment_to_reserved_columns!
# no assignment to structure fields
[left_column_name, right_column_name, depth_column_name].each do |column|
module_eval <<-"end_eval", __FILE__, __LINE__
def #{column}=(x)
raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
end
end_eval
end
end
end
end
end