locomotive-mongoid-tree 0.6.2
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.
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +192 -0
- data/Rakefile +31 -0
- data/lib/mongoid/tree.rb +318 -0
- data/lib/mongoid/tree/ordering.rb +195 -0
- data/lib/mongoid/tree/traversal.rb +119 -0
- data/spec/mongoid/tree/ordering_spec.rb +328 -0
- data/spec/mongoid/tree/traversal_spec.rb +175 -0
- data/spec/mongoid/tree_spec.rb +382 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/macros/tree_macros.rb +44 -0
- data/spec/support/models/node.rb +28 -0
- metadata +125 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Tree
|
3
|
+
##
|
4
|
+
# = Mongoid::Tree::Ordering
|
5
|
+
#
|
6
|
+
# Mongoid::Tree doesn't order the tree by default. To enable ordering of children
|
7
|
+
# include both Mongoid::Tree and Mongoid::Tree::Ordering into your document.
|
8
|
+
#
|
9
|
+
# == Utility methods
|
10
|
+
#
|
11
|
+
# This module adds methods to get related siblings depending on their position:
|
12
|
+
#
|
13
|
+
# node.lower_siblings
|
14
|
+
# node.higher_siblings
|
15
|
+
# node.first_sibling_in_list
|
16
|
+
# node.last_sibling_in_list
|
17
|
+
#
|
18
|
+
# There are several methods to move nodes around in the list:
|
19
|
+
#
|
20
|
+
# node.move_up
|
21
|
+
# node.move_down
|
22
|
+
# node.move_to_top
|
23
|
+
# node.move_to_bottom
|
24
|
+
# node.move_above(other)
|
25
|
+
# node.move_below(other)
|
26
|
+
#
|
27
|
+
# Additionally there are some methods to check aspects of the document
|
28
|
+
# in the list of children:
|
29
|
+
#
|
30
|
+
# node.at_top?
|
31
|
+
# node.at_bottom?
|
32
|
+
module Ordering
|
33
|
+
extend ActiveSupport::Concern
|
34
|
+
|
35
|
+
included do
|
36
|
+
field :position, :type => Integer
|
37
|
+
|
38
|
+
default_scope asc(:position)
|
39
|
+
|
40
|
+
before_save :assign_default_position
|
41
|
+
before_save :reposition_former_siblings, :if => :sibling_reposition_required?
|
42
|
+
after_destroy :move_lower_siblings_up
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Returns a chainable criteria for this document's ancestors
|
47
|
+
def ancestors
|
48
|
+
base_class.unscoped.where(:_id.in => parent_ids)
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Returns siblings below the current document.
|
53
|
+
# Siblings with a position greater than this documents's position.
|
54
|
+
def lower_siblings
|
55
|
+
self.siblings.where(:position.gt => self.position)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns siblings above the current document.
|
60
|
+
# Siblings with a position lower than this documents's position.
|
61
|
+
def higher_siblings
|
62
|
+
self.siblings.where(:position.lt => self.position)
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Returns the lowest sibling (could be self)
|
67
|
+
def last_sibling_in_list
|
68
|
+
siblings_and_self.last
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Returns the highest sibling (could be self)
|
73
|
+
def first_sibling_in_list
|
74
|
+
siblings_and_self.first
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Is this the highest sibling?
|
79
|
+
def at_top?
|
80
|
+
higher_siblings.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Is this the lowest sibling?
|
85
|
+
def at_bottom?
|
86
|
+
lower_siblings.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Move this node above all its siblings
|
91
|
+
def move_to_top
|
92
|
+
return true if at_top?
|
93
|
+
move_above(first_sibling_in_list)
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Move this node below all its siblings
|
98
|
+
def move_to_bottom
|
99
|
+
return true if at_bottom?
|
100
|
+
move_below(last_sibling_in_list)
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Move this node one position up
|
105
|
+
def move_up
|
106
|
+
return if at_top?
|
107
|
+
siblings.where(:position => self.position - 1).first.inc(:position, 1)
|
108
|
+
inc(:position, -1)
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Move this node one position down
|
113
|
+
def move_down
|
114
|
+
return if at_bottom?
|
115
|
+
siblings.where(:position => self.position + 1).first.inc(:position, -1)
|
116
|
+
inc(:position, 1)
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Move this node above the specified node
|
121
|
+
#
|
122
|
+
# This method changes the node's parent if nescessary.
|
123
|
+
def move_above(other)
|
124
|
+
unless sibling_of?(other)
|
125
|
+
self.parent_id = other.parent_id
|
126
|
+
save!
|
127
|
+
end
|
128
|
+
|
129
|
+
if position > other.position
|
130
|
+
new_position = other.position
|
131
|
+
other.lower_siblings.where(:position.lt => self.position).each { |s| s.inc(:position, 1) }
|
132
|
+
other.inc(:position, 1)
|
133
|
+
self.position = new_position
|
134
|
+
save!
|
135
|
+
else
|
136
|
+
new_position = other.position - 1
|
137
|
+
other.higher_siblings.where(:position.gt => self.position).each { |s| s.inc(:position, -1) }
|
138
|
+
self.position = new_position
|
139
|
+
save!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Move this node below the specified node
|
145
|
+
#
|
146
|
+
# This method changes the node's parent if nescessary.
|
147
|
+
def move_below(other)
|
148
|
+
unless sibling_of?(other)
|
149
|
+
self.parent_id = other.parent_id
|
150
|
+
save!
|
151
|
+
end
|
152
|
+
|
153
|
+
if position > other.position
|
154
|
+
new_position = other.position + 1
|
155
|
+
other.lower_siblings.where(:position.lt => self.position).each { |s| s.inc(:position, 1) }
|
156
|
+
self.position = new_position
|
157
|
+
save!
|
158
|
+
else
|
159
|
+
new_position = other.position
|
160
|
+
other.higher_siblings.where(:position.gt => self.position).each { |s| s.inc(:position, -1) }
|
161
|
+
other.inc(:position, -1)
|
162
|
+
self.position = new_position
|
163
|
+
save!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def move_lower_siblings_up
|
170
|
+
lower_siblings.each { |s| s.inc(:position, -1) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def reposition_former_siblings
|
174
|
+
former_siblings = base_class.where(:parent_id => attribute_was('parent_id')).
|
175
|
+
and(:position.gt => (attribute_was('position') || 0)).
|
176
|
+
excludes(:id => self.id)
|
177
|
+
former_siblings.each { |s| s.inc(:position, -1) }
|
178
|
+
end
|
179
|
+
|
180
|
+
def sibling_reposition_required?
|
181
|
+
parent_id_changed? && persisted?
|
182
|
+
end
|
183
|
+
|
184
|
+
def assign_default_position
|
185
|
+
return unless self.position.nil? || self.parent_id_changed?
|
186
|
+
|
187
|
+
if self.siblings.empty? || self.siblings.collect(&:position).compact.empty?
|
188
|
+
self.position = 0
|
189
|
+
else
|
190
|
+
self.position = self.siblings.max(:position).to_i + 1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Mongoid # :nodoc:
|
2
|
+
module Tree
|
3
|
+
##
|
4
|
+
# = Mongoid::Tree::Traversal
|
5
|
+
#
|
6
|
+
# Mongoid::Tree::Traversal provides a #traverse method to walk through the tree.
|
7
|
+
# It supports these traversal methods:
|
8
|
+
#
|
9
|
+
# * depth_first
|
10
|
+
# * breadth_first
|
11
|
+
#
|
12
|
+
# == Depth First Traversal
|
13
|
+
#
|
14
|
+
# See http://en.wikipedia.org/wiki/Depth-first_search for a proper description.
|
15
|
+
#
|
16
|
+
# Given a tree like:
|
17
|
+
#
|
18
|
+
# node1:
|
19
|
+
# - node2:
|
20
|
+
# - node3
|
21
|
+
# - node4:
|
22
|
+
# - node5
|
23
|
+
# - node6
|
24
|
+
# - node7
|
25
|
+
#
|
26
|
+
# Traversing the tree using depth first traversal would visit each node in this order:
|
27
|
+
#
|
28
|
+
# node1, node2, node3, node4, node5, node6, node7
|
29
|
+
#
|
30
|
+
# == Breadth First Traversal
|
31
|
+
#
|
32
|
+
# See http://en.wikipedia.org/wiki/Breadth-first_search for a proper description.
|
33
|
+
#
|
34
|
+
# Given a tree like:
|
35
|
+
#
|
36
|
+
# node1:
|
37
|
+
# - node2:
|
38
|
+
# - node5
|
39
|
+
# - node3:
|
40
|
+
# - node6
|
41
|
+
# - node7
|
42
|
+
# - node4
|
43
|
+
#
|
44
|
+
# Traversing the tree using breadth first traversal would visit each node in this order:
|
45
|
+
#
|
46
|
+
# node1, node2, node3, node4, node5, node6, node7
|
47
|
+
#
|
48
|
+
module Traversal
|
49
|
+
extend ActiveSupport::Concern
|
50
|
+
|
51
|
+
##
|
52
|
+
# :singleton-method: traverse
|
53
|
+
# Traverses the entire tree, one root at a time, using the given traversal
|
54
|
+
# method (Default is :depth_first).
|
55
|
+
#
|
56
|
+
# See Mongoid::Tree::Traversal for available traversal methods.
|
57
|
+
#
|
58
|
+
# Example:
|
59
|
+
#
|
60
|
+
# # Say we have the following tree, and want to print its hierarchy:
|
61
|
+
# # root_1
|
62
|
+
# # child_1_a
|
63
|
+
# # root_2
|
64
|
+
# # child_2_a
|
65
|
+
# # child_2_a_1
|
66
|
+
#
|
67
|
+
# Node.traverse(:depth_first) do |node|
|
68
|
+
# indentation = ' ' * node.depth
|
69
|
+
#
|
70
|
+
# puts "#{indentation}#{node.name}"
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
|
74
|
+
##
|
75
|
+
# The methods in this module are class-level methods documented above.
|
76
|
+
# They're extended into the base class automatically.
|
77
|
+
module ClassMethods # :nodoc:
|
78
|
+
def traverse(type = :depth_first, &block)
|
79
|
+
raise ArgumentError, "No block given" unless block_given?
|
80
|
+
roots.each { |root| root.traverse(type, &block) }
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Traverses the tree using the given traversal method (Default is :depth_first)
|
87
|
+
# and passes each document node to the block.
|
88
|
+
#
|
89
|
+
# See Mongoid::Tree::Traversal for available traversal methods.
|
90
|
+
#
|
91
|
+
# Example:
|
92
|
+
#
|
93
|
+
# results = []
|
94
|
+
# root.traverse(:depth_first) do |node|
|
95
|
+
# results << node
|
96
|
+
# end
|
97
|
+
def traverse(type = :depth_first, &block)
|
98
|
+
raise ArgumentError, "No block given" unless block_given?
|
99
|
+
send("#{type}_traversal", &block)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def depth_first_traversal(&block)
|
105
|
+
block.call(self)
|
106
|
+
self.children.each { |c| c.send(:depth_first_traversal, &block) }
|
107
|
+
end
|
108
|
+
|
109
|
+
def breadth_first_traversal(&block)
|
110
|
+
queue = [self]
|
111
|
+
while queue.any? do
|
112
|
+
node = queue.shift
|
113
|
+
block.call(node)
|
114
|
+
queue += node.children
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongoid::Tree::Ordering do
|
4
|
+
|
5
|
+
subject { OrderedNode }
|
6
|
+
|
7
|
+
it "should store position as an Integer with a default of nil" do
|
8
|
+
f = OrderedNode.fields['position']
|
9
|
+
f.should_not be_nil
|
10
|
+
f.options[:type].should == Integer
|
11
|
+
f.options[:default].should == nil
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'when saved' do
|
15
|
+
before(:each) do
|
16
|
+
setup_tree <<-ENDTREE
|
17
|
+
- root:
|
18
|
+
- child:
|
19
|
+
- subchild:
|
20
|
+
- subsubchild
|
21
|
+
- other_root:
|
22
|
+
- other_child
|
23
|
+
- another_child
|
24
|
+
ENDTREE
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should assign a default position of 0 to each node without a sibling" do
|
28
|
+
node(:child).position.should == 0
|
29
|
+
node(:subchild).position.should == 0
|
30
|
+
node(:subsubchild).position.should == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should place siblings at the end of the list by default" do
|
34
|
+
node(:root).position.should == 0
|
35
|
+
node(:other_root).position.should == 1
|
36
|
+
node(:other_child).position.should == 0
|
37
|
+
node(:another_child).position.should == 1
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should move a node to the end of a list when it is moved to a new parent" do
|
41
|
+
other_root = node(:other_root)
|
42
|
+
child = node(:child)
|
43
|
+
child.position.should == 0
|
44
|
+
other_root.children << child
|
45
|
+
child.reload
|
46
|
+
child.position.should == 2
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should correctly reposition siblings when one of them is removed" do
|
50
|
+
node(:other_child).destroy
|
51
|
+
node(:another_child).position.should == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should correctly reposition siblings when one of them is added to another parent" do
|
55
|
+
node(:root).children << node(:other_child)
|
56
|
+
node(:another_child).position.should == 0
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should correctly reposition siblings when the parent is changed" do
|
60
|
+
other_child = node(:other_child)
|
61
|
+
other_child.parent = node(:root)
|
62
|
+
other_child.save!
|
63
|
+
node(:another_child).position.should == 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not reposition siblings when it's not yet saved" do
|
67
|
+
new_node = OrderedNode.new(:name => 'new')
|
68
|
+
new_node.parent = node(:root)
|
69
|
+
new_node.should_not_receive(:reposition_former_siblings)
|
70
|
+
new_node.save
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'destroy strategies' do
|
75
|
+
before(:each) do
|
76
|
+
setup_tree <<-ENDTREE
|
77
|
+
- root:
|
78
|
+
- child:
|
79
|
+
- subchild
|
80
|
+
- other_child
|
81
|
+
- other_root
|
82
|
+
ENDTREE
|
83
|
+
end
|
84
|
+
|
85
|
+
describe ':move_children_to_parent' do
|
86
|
+
it "should set its childen's parent_id to the documents parent_id" do
|
87
|
+
node(:child).move_children_to_parent
|
88
|
+
node(:child).should be_leaf
|
89
|
+
node(:root).children.to_a.should == [node(:child), node(:other_child), node(:subchild)]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'utility methods' do
|
95
|
+
before(:each) do
|
96
|
+
setup_tree <<-ENDTREE
|
97
|
+
- first_root:
|
98
|
+
- first_child_of_first_root
|
99
|
+
- second_child_of_first_root
|
100
|
+
- second_root
|
101
|
+
- third_root
|
102
|
+
ENDTREE
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#lower_siblings' do
|
106
|
+
it "should return a collection of siblings lower on the list" do
|
107
|
+
node(:second_child_of_first_root).reload
|
108
|
+
node(:first_root).lower_siblings.to_a.should == [node(:second_root), node(:third_root)]
|
109
|
+
node(:second_root).lower_siblings.to_a.should == [node(:third_root)]
|
110
|
+
node(:third_root).lower_siblings.to_a.should == []
|
111
|
+
node(:first_child_of_first_root).lower_siblings.to_a.should == [node(:second_child_of_first_root)]
|
112
|
+
node(:second_child_of_first_root).lower_siblings.to_a.should == []
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#higher_siblings' do
|
117
|
+
it "should return a collection of siblings lower on the list" do
|
118
|
+
node(:first_root).higher_siblings.to_a.should == []
|
119
|
+
node(:second_root).higher_siblings.to_a.should == [node(:first_root)]
|
120
|
+
node(:third_root).higher_siblings.to_a.should == [node(:first_root), node(:second_root)]
|
121
|
+
node(:first_child_of_first_root).higher_siblings.to_a.should == []
|
122
|
+
node(:second_child_of_first_root).higher_siblings.to_a.should == [node(:first_child_of_first_root)]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#at_top?' do
|
127
|
+
it "should return true when the node is first in the list" do
|
128
|
+
node(:first_root).should be_at_top
|
129
|
+
node(:first_child_of_first_root).should be_at_top
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should return false when the node is not first in the list" do
|
133
|
+
node(:second_root).should_not be_at_top
|
134
|
+
node(:third_root).should_not be_at_top
|
135
|
+
node(:second_child_of_first_root).should_not be_at_top
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '#at_bottom?' do
|
140
|
+
it "should return true when the node is last in the list" do
|
141
|
+
node(:third_root).should be_at_bottom
|
142
|
+
node(:second_child_of_first_root).should be_at_bottom
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should return false when the node is not last in the list" do
|
146
|
+
node(:first_root).should_not be_at_bottom
|
147
|
+
node(:second_root).should_not be_at_bottom
|
148
|
+
node(:first_child_of_first_root).should_not be_at_bottom
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#last_sibling_in_list' do
|
153
|
+
it "should return the last sibling in the list containing the current sibling" do
|
154
|
+
node(:first_root).last_sibling_in_list.should == node(:third_root)
|
155
|
+
node(:second_root).last_sibling_in_list.should == node(:third_root)
|
156
|
+
node(:third_root).last_sibling_in_list.should == node(:third_root)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#first_sibling_in_list' do
|
161
|
+
it "should return the first sibling in the list containing the current sibling" do
|
162
|
+
node(:first_root).first_sibling_in_list.should == node(:first_root)
|
163
|
+
node(:second_root).first_sibling_in_list.should == node(:first_root)
|
164
|
+
node(:third_root).first_sibling_in_list.should == node(:first_root)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'ancestors' do
|
169
|
+
it "#ancestors should be returned in the correct order" do
|
170
|
+
setup_tree <<-ENDTREE
|
171
|
+
- root:
|
172
|
+
- level_1_a
|
173
|
+
- level_1_b:
|
174
|
+
- level_2_a:
|
175
|
+
- leaf
|
176
|
+
ENDTREE
|
177
|
+
|
178
|
+
node(:leaf).ancestors.to_a.should == [node(:root), node(:level_1_b), node(:level_2_a)]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'moving nodes around' do
|
184
|
+
before(:each) do
|
185
|
+
setup_tree <<-ENDTREE
|
186
|
+
- first_root:
|
187
|
+
- first_child_of_first_root
|
188
|
+
- second_child_of_first_root
|
189
|
+
- second_root:
|
190
|
+
- first_child_of_second_root
|
191
|
+
- third_root:
|
192
|
+
- first
|
193
|
+
- second
|
194
|
+
- third
|
195
|
+
ENDTREE
|
196
|
+
end
|
197
|
+
|
198
|
+
describe '#move_below' do
|
199
|
+
it 'should fix positions within the current list when moving an sibling away from its current parent' do
|
200
|
+
node_to_move = node(:first_child_of_first_root)
|
201
|
+
node_to_move.move_below(node(:first_child_of_second_root))
|
202
|
+
node(:second_child_of_first_root).position.should == 0
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should work when moving to a different parent' do
|
206
|
+
node_to_move = node(:first_child_of_first_root)
|
207
|
+
new_parent = node(:second_root)
|
208
|
+
node_to_move.move_below(node(:first_child_of_second_root))
|
209
|
+
node_to_move.reload
|
210
|
+
node_to_move.should be_at_bottom
|
211
|
+
node(:first_child_of_second_root).should be_at_top
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should be able to move the first node below the second node' do
|
215
|
+
first_node = node(:first_root)
|
216
|
+
second_node = node(:second_root)
|
217
|
+
first_node.move_below(second_node)
|
218
|
+
first_node.reload
|
219
|
+
second_node.reload
|
220
|
+
second_node.should be_at_top
|
221
|
+
first_node.higher_siblings.to_a.should == [second_node]
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'should be able to move the last node below the first node' do
|
225
|
+
first_node = node(:first_root)
|
226
|
+
last_node = node(:third_root)
|
227
|
+
last_node.move_below(first_node)
|
228
|
+
first_node.reload
|
229
|
+
last_node.reload
|
230
|
+
last_node.should_not be_at_bottom
|
231
|
+
node(:second_root).should be_at_bottom
|
232
|
+
last_node.higher_siblings.to_a.should == [first_node]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe '#move_above' do
|
237
|
+
it 'should fix positions within the current list when moving an sibling away from its current parent' do
|
238
|
+
node_to_move = node(:first_child_of_first_root)
|
239
|
+
node_to_move.move_above(node(:first_child_of_second_root))
|
240
|
+
node(:second_child_of_first_root).position.should == 0
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'should work when moving to a different parent' do
|
244
|
+
node_to_move = node(:first_child_of_first_root)
|
245
|
+
new_parent = node(:second_root)
|
246
|
+
node_to_move.move_above(node(:first_child_of_second_root))
|
247
|
+
node_to_move.reload
|
248
|
+
node_to_move.should be_at_top
|
249
|
+
node(:first_child_of_second_root).should be_at_bottom
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'should be able to move the last node above the second node' do
|
253
|
+
last_node = node(:third_root)
|
254
|
+
second_node = node(:second_root)
|
255
|
+
last_node.move_above(second_node)
|
256
|
+
last_node.reload
|
257
|
+
second_node.reload
|
258
|
+
second_node.should be_at_bottom
|
259
|
+
last_node.higher_siblings.to_a.should == [node(:first_root)]
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should be able to move the first node above the last node' do
|
263
|
+
first_node = node(:first_root)
|
264
|
+
last_node = node(:third_root)
|
265
|
+
first_node.move_above(last_node)
|
266
|
+
first_node.reload
|
267
|
+
last_node.reload
|
268
|
+
node(:second_root).should be_at_top
|
269
|
+
first_node.higher_siblings.to_a.should == [node(:second_root)]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe "#move_to_top" do
|
274
|
+
it "should return true when attempting to move the first sibling" do
|
275
|
+
node(:first_root).move_to_top.should == true
|
276
|
+
node(:first_child_of_first_root).move_to_top.should == true
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should be able to move the last sibling to the top" do
|
280
|
+
first_node = node(:first_root)
|
281
|
+
last_node = node(:third_root)
|
282
|
+
last_node.move_to_top
|
283
|
+
first_node.reload
|
284
|
+
last_node.should be_at_top
|
285
|
+
first_node.should_not be_at_top
|
286
|
+
first_node.higher_siblings.to_a.should == [last_node]
|
287
|
+
last_node.lower_siblings.to_a.should == [first_node, node(:second_root)]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "#move_to_bottom" do
|
292
|
+
it "should return true when attempting to move the last sibling" do
|
293
|
+
node(:third_root).move_to_bottom.should == true
|
294
|
+
node(:second_child_of_first_root).move_to_bottom.should == true
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should be able to move the first sibling to the bottom" do
|
298
|
+
first_node = node(:first_root)
|
299
|
+
middle_node = node(:second_root)
|
300
|
+
last_node = node(:third_root)
|
301
|
+
first_node.move_to_bottom
|
302
|
+
middle_node.reload
|
303
|
+
last_node.reload
|
304
|
+
first_node.should_not be_at_top
|
305
|
+
first_node.should be_at_bottom
|
306
|
+
last_node.should_not be_at_bottom
|
307
|
+
last_node.should_not be_at_top
|
308
|
+
middle_node.should be_at_top
|
309
|
+
first_node.lower_siblings.to_a.should == []
|
310
|
+
last_node.higher_siblings.to_a.should == [middle_node]
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe "#move_up" do
|
315
|
+
it "should correctly move nodes up" do
|
316
|
+
node(:third).move_up
|
317
|
+
node(:third_root).children.should == [node(:first), node(:third), node(:second)]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
describe "#move_down" do
|
322
|
+
it "should correctly move nodes down" do
|
323
|
+
node(:first).move_down
|
324
|
+
node(:third_root).children.should == [node(:second), node(:first), node(:third)]
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end # moving nodes around
|
328
|
+
end # Mongoid::Tree::Ordering
|