mongoid_nested_set 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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