mongoid_nested_set 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ module Mongoid::Acts::NestedSet
2
+
3
+ # Mixed int both classes and instances to provide easy access to the field names
4
+ module Fields
5
+
6
+ def left_field_name
7
+ acts_as_nested_set_options[:left_field]
8
+ end
9
+
10
+
11
+ def right_field_name
12
+ acts_as_nested_set_options[:right_field]
13
+ end
14
+
15
+
16
+ def parent_field_name
17
+ acts_as_nested_set_options[:parent_field]
18
+ end
19
+
20
+
21
+ def outline_number_field_name
22
+ acts_as_nested_set_options[:outline_number_field]
23
+ end
24
+
25
+
26
+ def scope_field_names
27
+ Array(acts_as_nested_set_options[:scope])
28
+ end
29
+
30
+
31
+ def scope_class
32
+ acts_as_nested_set_options[:klass]
33
+ end
34
+
35
+
36
+ def quoted_left_field_name
37
+ # TODO
38
+ left_field_name
39
+ end
40
+
41
+
42
+ def quoted_right_field_name
43
+ # TODO
44
+ right_field_name
45
+ end
46
+
47
+
48
+ def quoted_parent_field_name
49
+ # TODO
50
+ parent_field_name
51
+ end
52
+
53
+
54
+ def quoted_scope_field_names
55
+ # TODO
56
+ scope_field_names
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,122 @@
1
+ module Mongoid::Acts::NestedSet
2
+
3
+ module OutlineNumber
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.send(:include, InstanceMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Iterates over tree elements and determines the current outline number in
13
+ # the tree.
14
+ # Only accepts default ordering, ordering by an other field than lft
15
+ # does not work.
16
+ # This method does not used the cached number field.
17
+ #
18
+ # Example:
19
+ # Category.each_with_outline_number(Category.root.self_and_descendants) do |o, level|
20
+ #
21
+ def each_with_outline_number(objects, parent_number=nil)
22
+ objects = Array(objects) unless objects.is_a? Array
23
+
24
+ stack = []
25
+ last_num = parent_number
26
+ objects.each_with_index do |o, i|
27
+ if i == 0 && last_num == nil && !o.root?
28
+ last_num = o.parent.outline_number
29
+ end
30
+
31
+ if stack.last.nil? || o.parent_id != stack.last[:parent_id]
32
+ # we are on a new level, did we descend or ascend?
33
+ if stack.any? { |h| h[:parent_id] == o.parent_id }
34
+ # ascend
35
+ stack.pop while stack.last[:parent_id] != o.parent_id
36
+ else
37
+ # descend
38
+ stack << {:parent_id => o.parent_id, :parent_number => last_num, :siblings => []}
39
+ end
40
+ end
41
+
42
+ if o.root? && !roots_have_outline_numbers?
43
+ num = nil
44
+ else
45
+ num = o.send(:build_outline_number,
46
+ o.root? ? '' : stack.last[:parent_number],
47
+ o.send(:outline_number_sequence, stack.last[:siblings])
48
+ )
49
+ end
50
+ yield(o, num)
51
+
52
+ stack.last[:siblings] << o
53
+ last_num = num
54
+ end
55
+ end
56
+
57
+
58
+ def update_outline_numbers(objects, parent_number=nil)
59
+ each_with_outline_number(objects, parent_number) do |o, num|
60
+ o.update_attributes(outline_number_field_name => num)
61
+ end
62
+ end
63
+
64
+
65
+ # Do root nodes have outline numbers
66
+ def roots_have_outline_numbers?
67
+ false
68
+ end
69
+
70
+ end
71
+
72
+ module InstanceMethods
73
+
74
+ def outline_number
75
+ self[outline_number_field_name]
76
+ end
77
+
78
+
79
+ def update_outline_number
80
+ self.class.update_outline_numbers(self)
81
+ end
82
+
83
+
84
+ def update_self_and_descendants_outline_number
85
+ self.class.update_outline_numbers(self_and_descendants)
86
+ end
87
+
88
+
89
+ def update_descendants_outline_number
90
+ self.class.update_outline_numbers(self.descendants, self.outline_number)
91
+ end
92
+
93
+
94
+ protected
95
+
96
+ # Gets the outline sequence number for this node
97
+ #
98
+ # For example, if the parent's outline number is 1.2 and this is the
99
+ # 3rd sibling this will return 3.
100
+ #
101
+ def outline_number_sequence(prev_siblings)
102
+ prev_siblings.count + 1
103
+ end
104
+
105
+
106
+ # Constructs the full outline number
107
+ #
108
+ def build_outline_number(parent_number, sequence)
109
+ if parent_number && parent_number != ''
110
+ parent_number + outline_number_seperator + sequence.to_s
111
+ else
112
+ sequence.to_s
113
+ end
114
+ end
115
+
116
+ def outline_number_seperator
117
+ '.'
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,41 @@
1
+ module Mongoid::Acts::NestedSet
2
+
3
+ module Rebuild
4
+
5
+ # Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
6
+ # Warning: Very expensive!
7
+ def rebuild!(options = {})
8
+ # Don't rebuild a valid tree.
9
+ return true if valid?
10
+
11
+ scope = lambda{ |node| {} }
12
+ if acts_as_nested_set_options[:scope]
13
+ scope = lambda { |node| node.nested_set_scope.scoped }
14
+ end
15
+ indices = {}
16
+
17
+ set_left_and_rights = lambda do |node|
18
+ # set left
19
+ left = (indices[scope.call(node)] += 1)
20
+ # find
21
+ node.nested_set_scope.where(parent_field_name => node.id).asc(left_field_name).asc(right_field_name).each { |n| set_left_and_rights.call(n) }
22
+ # set right
23
+ right = (indices[scope.call(node)] += 1)
24
+
25
+ node.class.collection.update(
26
+ {:_id => node.id},
27
+ {"$set" => {left_field_name => left, right_field_name => right}},
28
+ {:safe => true}
29
+ )
30
+ end
31
+
32
+ # Find root node(s)
33
+ root_nodes = self.where(parent_field_name => nil).asc(left_field_name).asc(right_field_name).asc(:_id).each do |root_node|
34
+ # setup index for this scope
35
+ indices[scope.call(root_node)] ||= 0
36
+ set_left_and_rights.call(root_node)
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,100 @@
1
+ module Mongoid::Acts::NestedSet
2
+
3
+ module Relations
4
+
5
+ # Returns root
6
+ def root
7
+ self_and_ancestors.first
8
+ end
9
+
10
+
11
+ # Returns the array of all parents and self
12
+ def self_and_ancestors
13
+ nested_set_scope.where(
14
+ left_field_name => {"$lte" => left},
15
+ right_field_name => {"$gte" => right}
16
+ )
17
+ end
18
+
19
+
20
+ # Returns an array of all parents
21
+ def ancestors
22
+ without_self self_and_ancestors
23
+ end
24
+
25
+
26
+ # Returns the array of all children of the parent, including self
27
+ def self_and_siblings
28
+ nested_set_scope.where(parent_field_name => parent_id)
29
+ end
30
+
31
+
32
+ # Returns the array of all children of the parent, except self
33
+ def siblings
34
+ without_self self_and_siblings
35
+ end
36
+
37
+
38
+ # Returns a set of all of its nested children which do not have children
39
+ def leaves
40
+ descendants.where("this.#{right_field_name} - this.#{left_field_name} == 1")
41
+ end
42
+
43
+
44
+ # Returns the level of this object in the tree
45
+ # root level is 0
46
+ def level
47
+ parent_id.nil? ? 0 : ancestors.count
48
+ end
49
+
50
+
51
+ # Returns a set of itself and all of its nested children
52
+ def self_and_descendants
53
+ nested_set_scope.where(
54
+ left_field_name => {"$gte" => left},
55
+ right_field_name => {"$lte" => right}
56
+ )
57
+ end
58
+
59
+
60
+ # Returns a set of all of its children and nested children
61
+ def descendants
62
+ without_self self_and_descendants
63
+ end
64
+
65
+
66
+ def is_descendant_of?(other)
67
+ other.left < self.left && self.left < other.right && same_scope?(other)
68
+ end
69
+ alias :descendant_of? is_descendant_of?
70
+
71
+
72
+ def is_or_is_descendant_of?(other)
73
+ other.left <= self.left && self.left < other.right && same_scope?(other)
74
+ end
75
+
76
+
77
+ def is_ancestor_of?(other)
78
+ self.left < other.left && other.left < self.right && same_scope?(other)
79
+ end
80
+ alias :ancestor_of? is_ancestor_of?
81
+
82
+
83
+ def is_or_is_ancestor_of?(other)
84
+ self.left <= other.left && other.left < self.right && same_scope?(other)
85
+ end
86
+
87
+
88
+ # Find the first sibling to the left
89
+ def left_sibling
90
+ siblings.where(left_field_name => {"$lt" => left}).remove_order_by.desc(left_field_name).first
91
+ end
92
+
93
+
94
+ # Find the first sibling to the right
95
+ def right_sibling
96
+ siblings.where(left_field_name => {"$gt" => left}).asc(left_field_name).first
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,11 @@
1
+ module Mongoid
2
+ module Criterion
3
+ module Optional
4
+ def remove_order_by
5
+ @options[:sort] = nil
6
+ self
7
+ end
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,233 @@
1
+ module Mongoid::Acts::NestedSet
2
+
3
+ module Update
4
+
5
+ # Shorthand method for finding the left sibling and moving to the left of it
6
+ def move_left
7
+ move_to_left_of left_sibling
8
+ end
9
+
10
+
11
+ # Shorthand method for finding the right sibling and moving to the right of it
12
+ def move_right
13
+ move_to_right_of right_sibling
14
+ end
15
+
16
+
17
+ # Move the node to the left of another node (you can pass id only)
18
+ def move_to_left_of(node)
19
+ move_to node, :left
20
+ end
21
+
22
+
23
+ # Move the node to the right of another node (you can pass id only)
24
+ def move_to_right_of(node)
25
+ move_to node, :right
26
+ end
27
+
28
+
29
+ # Move the node to the child of another node (you can pass id only)
30
+ def move_to_child_of(node)
31
+ move_to node, :child
32
+ end
33
+
34
+
35
+ # Move the node to root nodes
36
+ def move_to_root
37
+ move_to nil, :root
38
+ end
39
+
40
+
41
+ def move_possible?(target)
42
+ self != target && # Can't target self
43
+ same_scope?(target) && # can't be in different scopes
44
+ !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
45
+ end
46
+
47
+
48
+
49
+ protected
50
+
51
+ def store_new_parent
52
+ @move_to_new_parent_id = ((self.new_record? && parent_id) || send("#{parent_field_name}_changed?")) ? parent_id : false
53
+ true # force callback to return true
54
+ end
55
+
56
+
57
+ def move_to_new_parent
58
+ if @move_to_new_parent_id.nil?
59
+ move_to_root
60
+ elsif @move_to_new_parent_id
61
+ move_to_child_of(@move_to_new_parent_id)
62
+ end
63
+ end
64
+
65
+
66
+ # on creation, set automatically lft and rgt to the end of the tree
67
+ def set_default_left_and_right
68
+ maxright = nested_set_scope.max(right_field_name) || 0
69
+ self[left_field_name] = maxright + 1
70
+ self[right_field_name] = maxright + 2
71
+ self[:depth] = 0
72
+ end
73
+
74
+
75
+ def move_to(target, position)
76
+ raise Mongoid::Errors::MongoidError, "You cannot move a new node" if self.new_record?
77
+
78
+ res = run_callbacks :move do
79
+
80
+ # No transaction support in MongoDB.
81
+ # ACID is not guaranteed
82
+ # TODO
83
+
84
+ if target.is_a? scope_class
85
+ target.reload_nested_set
86
+ elsif position != :root
87
+ # load object if node is not an object
88
+ target = nested_set_scope.where(:_id => target).first
89
+ end
90
+ self.reload_nested_set
91
+
92
+ unless position == :root || target
93
+ raise Mongoid::Errors::MongoidError, "Impossible move, target node cannot be found."
94
+ end
95
+
96
+ unless position == :root || move_possible?(target)
97
+ raise Mongoid::Errors::MongoidError, "Impossible move, target node cannot be inside moved tree."
98
+ end
99
+
100
+ bound = case position
101
+ when :child; target[right_field_name]
102
+ when :left; target[left_field_name]
103
+ when :right; target[right_field_name] + 1
104
+ when :root; 1
105
+ else raise Mongoid::Errors::MongoidError, "Position should be :child, :left, :right or :root ('#{position}' received)."
106
+ end
107
+
108
+ if bound > self[right_field_name]
109
+ bound = bound - 1
110
+ other_bound = self[right_field_name] + 1
111
+ else
112
+ other_bound = self[left_field_name] - 1
113
+ end
114
+
115
+ # there would be no change
116
+ return self if bound == self[right_field_name] || bound == self[left_field_name]
117
+
118
+ # we have defined the boundaries of two non-overlapping intervals,
119
+ # so sorting puts both the intervals and their boundaries in order
120
+ a, b, c, d = [self[left_field_name], self[right_field_name], bound, other_bound].sort
121
+
122
+ old_parent = self[parent_field_name]
123
+ new_parent = case position
124
+ when :child; target.id
125
+ when :root; nil
126
+ else target[parent_field_name]
127
+ end
128
+
129
+ # TODO: Worst case O(n) queries, improve?
130
+ # MongoDB 1.9 may allow javascript in updates: http://jira.mongodb.org/browse/SERVER-458
131
+ nested_set_scope.only(left_field_name, right_field_name, parent_field_name).remove_order_by.each do |node|
132
+ updates = {}
133
+ if (a..b).include? node.left
134
+ updates[left_field_name] = node.left + d - b
135
+ elsif (c..d).include? node.left
136
+ updates[left_field_name] = node.left + a - c
137
+ end
138
+
139
+ if (a..b).include? node.right
140
+ updates[right_field_name] = node.right + d - b
141
+ elsif (c..d).include? node.right
142
+ updates[right_field_name] = node.right + a - c
143
+ end
144
+
145
+ updates[parent_field_name] = new_parent if self.id == node.id
146
+
147
+ node.class.collection.update(
148
+ {:_id => node.id },
149
+ {"$set" => updates},
150
+ {:safe => true}
151
+ ) unless updates.empty?
152
+ end
153
+
154
+ self.reload_nested_set
155
+ self.update_self_and_descendants_depth
156
+
157
+ if outline_numbering?
158
+ if old_parent && old_parent != new_parent
159
+ scope_class.where(:_id => old_parent).first.update_descendants_outline_number
160
+ end
161
+ if new_parent
162
+ scope_class.where(:_id => new_parent).first.update_descendants_outline_number
163
+ else
164
+ update_self_and_descendants_outline_number
165
+ end
166
+ self.reload_nested_set
167
+ end
168
+
169
+ target.reload_nested_set if target
170
+ end
171
+ self
172
+ end
173
+
174
+
175
+ # Update cached level attribute
176
+ def update_depth
177
+ if depth?
178
+ self.update_attributes(:depth => level)
179
+ end
180
+ self
181
+ end
182
+
183
+
184
+ # Update cached level attribute for self and descendants
185
+ def update_self_and_descendants_depth
186
+ if depth?
187
+ scope_class.each_with_level(self_and_descendants) do |node, level|
188
+ node.class.collection.update(
189
+ {:_id => node.id},
190
+ {"$set" => {:depth => level}},
191
+ {:safe => true}
192
+ ) unless node.depth == level
193
+ end
194
+ self.reload
195
+ end
196
+ self
197
+ end
198
+
199
+
200
+ # Prunes a branch off of the tree, shifting all of the elements on the right
201
+ # back to the left so the counts still work
202
+ def destroy_descendants
203
+ return if right.nil? || left.nil? || skip_before_destroy
204
+
205
+ if acts_as_nested_set_options[:dependent] == :destroy
206
+ descendants.each do |model|
207
+ model.skip_before_destroy = true
208
+ model.destroy
209
+ end
210
+ else
211
+ c = nested_set_scope.fuse(:where => {left_field_name => {"$gt" => left}, right_field_name => {"$lt" => right}})
212
+ scope_class.delete_all(:conditions => c.selector)
213
+ end
214
+
215
+ # update lefts and rights for remaining nodes
216
+ diff = right - left + 1
217
+ scope_class.collection.update(
218
+ nested_set_scope.fuse(:where => {left_field_name => {"$gt" => right}}).selector,
219
+ {"$inc" => { left_field_name => -diff }},
220
+ {:safe => true, :multi => true}
221
+ )
222
+ scope_class.collection.update(
223
+ nested_set_scope.fuse(:where => {right_field_name => {"$gt" => right}}).selector,
224
+ {"$inc" => { right_field_name => -diff }},
225
+ {:safe => true, :multi => true}
226
+ )
227
+
228
+ # Don't allow multiple calls to destroy to corrupt the set
229
+ self.skip_before_destroy = true
230
+ end
231
+
232
+ end
233
+ end