mongoid-tree-rational 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ module Mongoid
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
+ ##
53
+ # This module implements class methods that will be available
54
+ # on the document that includes Mongoid::Tree::Traversal
55
+ module ClassMethods
56
+ ##
57
+ # Traverses the entire tree, one root at a time, using the given traversal
58
+ # method (Default is :depth_first).
59
+ #
60
+ # See Mongoid::Tree::Traversal for available traversal methods.
61
+ #
62
+ # @example
63
+ #
64
+ # # Say we have the following tree, and want to print its hierarchy:
65
+ # # root_1
66
+ # # child_1_a
67
+ # # root_2
68
+ # # child_2_a
69
+ # # child_2_a_1
70
+ #
71
+ # Node.traverse(:depth_first) do |node|
72
+ # indentation = ' ' * node.depth
73
+ #
74
+ # puts "#{indentation}#{node.name}"
75
+ # end
76
+ #
77
+ def traverse(type = :depth_first, &block)
78
+ roots.collect { |root| root.traverse(type, &block) }.flatten
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Traverses the tree using the given traversal method (Default is :depth_first)
84
+ # and passes each document node to the block.
85
+ #
86
+ # See Mongoid::Tree::Traversal for available traversal methods.
87
+ #
88
+ # @example
89
+ #
90
+ # results = []
91
+ # root.traverse(:depth_first) do |node|
92
+ # results << node
93
+ # end
94
+ #
95
+ # root.traverse(:depth_first).map(&:name)
96
+ # root.traverse(:depth_first, &:name)
97
+ #
98
+ def traverse(type = :depth_first, &block)
99
+ block ||= lambda { |node| node }
100
+ send("#{type}_traversal", &block)
101
+ end
102
+
103
+ private
104
+
105
+ def depth_first_traversal(&block)
106
+ result = [block.call(self)] + self.children.collect { |c| c.send(:depth_first_traversal, &block) }
107
+ result.flatten
108
+ end
109
+
110
+ def breadth_first_traversal(&block)
111
+ result = []
112
+ queue = [self]
113
+ while queue.any? do
114
+ node = queue.shift
115
+ result << block.call(node)
116
+ queue += node.children
117
+ end
118
+ result
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,103 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "mongoid-tree-rational"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Leif Ringstad", "Benedikt Deicke"]
12
+ s.date = "2013-10-09"
13
+ s.description = "A tree structure for Mongoid documents using the materialized path pattern and rational number sorting."
14
+ s.email = "leifcr@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".rspec",
21
+ ".travis.yml",
22
+ "Gemfile",
23
+ "Guardfile",
24
+ "LICENSE",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/mongoid/locale/en.yml",
29
+ "lib/mongoid/locale/nb.yml",
30
+ "lib/mongoid/tree.rb",
31
+ "lib/mongoid/tree/ordering.rb",
32
+ "lib/mongoid/tree/rational_numbering.rb",
33
+ "lib/mongoid/tree/traversal.rb",
34
+ "mongoid-tree-rational.gemspec",
35
+ "spec/mongoid/tree/ordering_spec.rb",
36
+ "spec/mongoid/tree/rational_numbering_spec.rb",
37
+ "spec/mongoid/tree/traversal_spec.rb",
38
+ "spec/mongoid/tree_spec.rb",
39
+ "spec/spec_helper.rb",
40
+ "spec/support/macros/tree_macros.rb",
41
+ "spec/support/models/node.rb"
42
+ ]
43
+ s.homepage = "https://github.com/boxcms/mongoid-tree-rational"
44
+ s.licenses = ["MIT"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = "1.8.25"
47
+ s.summary = "A tree structure for Mongoid documents with rational numbers"
48
+
49
+ if s.respond_to? :specification_version then
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<mongoid>, ["<= 4.0", ">= 3.0"])
54
+ s.add_runtime_dependency(%q<rational_number>, [">= 0"])
55
+ s.add_development_dependency(%q<rake>, [">= 0"])
56
+ s.add_development_dependency(%q<rspec>, [">= 0"])
57
+ s.add_development_dependency(%q<yard>, [">= 0"])
58
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
59
+ s.add_development_dependency(%q<guard-rspec>, [">= 2.6.0"])
60
+ s.add_development_dependency(%q<rb-inotify>, [">= 0"])
61
+ s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
62
+ s.add_development_dependency(%q<wdm>, [">= 0"])
63
+ s.add_development_dependency(%q<hirb>, [">= 0"])
64
+ s.add_development_dependency(%q<wirble>, [">= 0"])
65
+ s.add_development_dependency(%q<awesome_print>, [">= 0"])
66
+ s.add_development_dependency(%q<coveralls>, [">= 0"])
67
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
68
+ else
69
+ s.add_dependency(%q<mongoid>, ["<= 4.0", ">= 3.0"])
70
+ s.add_dependency(%q<rational_number>, [">= 0"])
71
+ s.add_dependency(%q<rake>, [">= 0"])
72
+ s.add_dependency(%q<rspec>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ s.add_dependency(%q<jeweler>, [">= 0"])
75
+ s.add_dependency(%q<guard-rspec>, [">= 2.6.0"])
76
+ s.add_dependency(%q<rb-inotify>, [">= 0"])
77
+ s.add_dependency(%q<rb-fsevent>, [">= 0"])
78
+ s.add_dependency(%q<wdm>, [">= 0"])
79
+ s.add_dependency(%q<hirb>, [">= 0"])
80
+ s.add_dependency(%q<wirble>, [">= 0"])
81
+ s.add_dependency(%q<awesome_print>, [">= 0"])
82
+ s.add_dependency(%q<coveralls>, [">= 0"])
83
+ s.add_dependency(%q<simplecov>, [">= 0"])
84
+ end
85
+ else
86
+ s.add_dependency(%q<mongoid>, ["<= 4.0", ">= 3.0"])
87
+ s.add_dependency(%q<rational_number>, [">= 0"])
88
+ s.add_dependency(%q<rake>, [">= 0"])
89
+ s.add_dependency(%q<rspec>, [">= 0"])
90
+ s.add_dependency(%q<yard>, [">= 0"])
91
+ s.add_dependency(%q<jeweler>, [">= 0"])
92
+ s.add_dependency(%q<guard-rspec>, [">= 2.6.0"])
93
+ s.add_dependency(%q<rb-inotify>, [">= 0"])
94
+ s.add_dependency(%q<rb-fsevent>, [">= 0"])
95
+ s.add_dependency(%q<wdm>, [">= 0"])
96
+ s.add_dependency(%q<hirb>, [">= 0"])
97
+ s.add_dependency(%q<wirble>, [">= 0"])
98
+ s.add_dependency(%q<awesome_print>, [">= 0"])
99
+ s.add_dependency(%q<coveralls>, [">= 0"])
100
+ s.add_dependency(%q<simplecov>, [">= 0"])
101
+ end
102
+ end
103
+
@@ -0,0 +1,342 @@
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
+ expect(f).not_to be_nil
10
+ expect(f.options[:type]).to eq(Integer)
11
+ expect(f.options[:default]).not_to be
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
+ expect(node(:child).position).to eq(0)
29
+ expect(node(:subchild).position).to eq(0)
30
+ expect(node(:subsubchild).position).to eq(0)
31
+ end
32
+
33
+ it "should place siblings at the end of the list by default" do
34
+ expect(node(:root).position).to eq(0)
35
+ expect(node(:other_root).position).to eq(1)
36
+ expect(node(:other_child).position).to eq(0)
37
+ expect(node(:another_child).position).to eq(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
+ expect(child.position).to eq(0)
44
+ other_root.children << child
45
+ child.reload
46
+ expect(child.position).to eq(2)
47
+ end
48
+
49
+ it "should correctly reposition siblings when one of them is removed" do
50
+ node(:other_child).destroy
51
+ expect(node(:another_child).position).to eq(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
+ expect(node(:another_child).position).to eq(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
+ expect(node(:another_child).position).to eq(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
+ expect(new_node).not_to 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
+ expect(node(:child)).to be_leaf
89
+ expect(node(:root).children.to_a).to eq([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
+ expect(node(:first_root).lower_siblings.to_a).to eq([node(:second_root), node(:third_root)])
109
+ expect(node(:second_root).lower_siblings.to_a).to eq([node(:third_root)])
110
+ expect(node(:third_root).lower_siblings.to_a).to eq([])
111
+ expect(node(:first_child_of_first_root).lower_siblings.to_a).to eq([node(:second_child_of_first_root)])
112
+ expect(node(:second_child_of_first_root).lower_siblings.to_a).to eq([])
113
+ end
114
+ end
115
+
116
+ describe '#higher_siblings' do
117
+ it "should return a collection of siblings lower on the list" do
118
+ expect(node(:first_root).higher_siblings.to_a).to eq([])
119
+ expect(node(:second_root).higher_siblings.to_a).to eq([node(:first_root)])
120
+ expect(node(:third_root).higher_siblings.to_a).to eq([node(:first_root), node(:second_root)])
121
+ expect(node(:first_child_of_first_root).higher_siblings.to_a).to eq([])
122
+ expect(node(:second_child_of_first_root).higher_siblings.to_a).to eq([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
+ expect(node(:first_root)).to be_at_top
129
+ expect(node(:first_child_of_first_root)).to be_at_top
130
+ end
131
+
132
+ it "should return false when the node is not first in the list" do
133
+ expect(node(:second_root)).not_to be_at_top
134
+ expect(node(:third_root)).not_to be_at_top
135
+ expect(node(:second_child_of_first_root)).not_to 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
+ expect(node(:third_root)).to be_at_bottom
142
+ expect(node(:second_child_of_first_root)).to be_at_bottom
143
+ end
144
+
145
+ it "should return false when the node is not last in the list" do
146
+ expect(node(:first_root)).not_to be_at_bottom
147
+ expect(node(:second_root)).not_to be_at_bottom
148
+ expect(node(:first_child_of_first_root)).not_to 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
+ expect(node(:first_root).last_sibling_in_list).to eq(node(:third_root))
155
+ expect(node(:second_root).last_sibling_in_list).to eq(node(:third_root))
156
+ expect(node(:third_root).last_sibling_in_list).to eq(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
+ expect(node(:first_root).first_sibling_in_list).to eq(node(:first_root))
163
+ expect(node(:second_root).first_sibling_in_list).to eq(node(:first_root))
164
+ expect(node(:third_root).first_sibling_in_list).to eq(node(:first_root))
165
+ end
166
+ end
167
+
168
+ describe '#ancestors' do
169
+ it "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
+ expect(node(:leaf).ancestors.to_a).to eq([node(:root), node(:level_1_b), node(:level_2_a)])
179
+ end
180
+
181
+ it "should return the ancestors in correct order even after rearranging" do
182
+ setup_tree <<-ENDTREE
183
+ - root:
184
+ - child:
185
+ - subchild
186
+ ENDTREE
187
+
188
+ child = node(:child); child.parent = nil; child.save!
189
+ root = node(:root); root.parent = node(:child); root.save!
190
+ subchild = node(:subchild); subchild.parent = root; subchild.save!
191
+
192
+ expect(subchild.ancestors.to_a).to eq([child, root])
193
+ end
194
+ end
195
+ end
196
+
197
+ describe 'moving nodes around' do
198
+ before(:each) do
199
+ setup_tree <<-ENDTREE
200
+ - first_root:
201
+ - first_child_of_first_root
202
+ - second_child_of_first_root
203
+ - second_root:
204
+ - first_child_of_second_root
205
+ - third_root:
206
+ - first
207
+ - second
208
+ - third
209
+ ENDTREE
210
+ end
211
+
212
+ describe '#move_below' do
213
+ it 'should fix positions within the current list when moving an sibling away from its current parent' do
214
+ node_to_move = node(:first_child_of_first_root)
215
+ node_to_move.move_below(node(:first_child_of_second_root))
216
+ expect(node(:second_child_of_first_root).position).to eq(0)
217
+ end
218
+
219
+ it 'should work when moving to a different parent' do
220
+ node_to_move = node(:first_child_of_first_root)
221
+ new_parent = node(:second_root)
222
+ node_to_move.move_below(node(:first_child_of_second_root))
223
+ node_to_move.reload
224
+ expect(node_to_move).to be_at_bottom
225
+ expect(node(:first_child_of_second_root)).to be_at_top
226
+ end
227
+
228
+ it 'should be able to move the first node below the second node' do
229
+ first_node = node(:first_root)
230
+ second_node = node(:second_root)
231
+ first_node.move_below(second_node)
232
+ first_node.reload
233
+ second_node.reload
234
+ expect(second_node).to be_at_top
235
+ expect(first_node.higher_siblings.to_a).to eq([second_node])
236
+ end
237
+
238
+ it 'should be able to move the last node below the first node' do
239
+ first_node = node(:first_root)
240
+ last_node = node(:third_root)
241
+ last_node.move_below(first_node)
242
+ first_node.reload
243
+ last_node.reload
244
+ expect(last_node).not_to be_at_bottom
245
+ expect(node(:second_root)).to be_at_bottom
246
+ expect(last_node.higher_siblings.to_a).to eq([first_node])
247
+ end
248
+ end
249
+
250
+ describe '#move_above' do
251
+ it 'should fix positions within the current list when moving an sibling away from its current parent' do
252
+ node_to_move = node(:first_child_of_first_root)
253
+ node_to_move.move_above(node(:first_child_of_second_root))
254
+ expect(node(:second_child_of_first_root).position).to eq(0)
255
+ end
256
+
257
+ it 'should work when moving to a different parent' do
258
+ node_to_move = node(:first_child_of_first_root)
259
+ new_parent = node(:second_root)
260
+ node_to_move.move_above(node(:first_child_of_second_root))
261
+ node_to_move.reload
262
+ expect(node_to_move).to be_at_top
263
+ expect(node(:first_child_of_second_root)).to be_at_bottom
264
+ end
265
+
266
+ it 'should be able to move the last node above the second node' do
267
+ last_node = node(:third_root)
268
+ second_node = node(:second_root)
269
+ last_node.move_above(second_node)
270
+ last_node.reload
271
+ second_node.reload
272
+ expect(second_node).to be_at_bottom
273
+ expect(last_node.higher_siblings.to_a).to eq([node(:first_root)])
274
+ end
275
+
276
+ it 'should be able to move the first node above the last node' do
277
+ first_node = node(:first_root)
278
+ last_node = node(:third_root)
279
+ first_node.move_above(last_node)
280
+ first_node.reload
281
+ last_node.reload
282
+ expect(node(:second_root)).to be_at_top
283
+ expect(first_node.higher_siblings.to_a).to eq([node(:second_root)])
284
+ end
285
+ end
286
+
287
+ describe "#move_to_top" do
288
+ it "should return true when attempting to move the first sibling" do
289
+ expect(node(:first_root).move_to_top).to eq(true)
290
+ expect(node(:first_child_of_first_root).move_to_top).to eq(true)
291
+ end
292
+
293
+ it "should be able to move the last sibling to the top" do
294
+ first_node = node(:first_root)
295
+ last_node = node(:third_root)
296
+ last_node.move_to_top
297
+ first_node.reload
298
+ expect(last_node).to be_at_top
299
+ expect(first_node).not_to be_at_top
300
+ expect(first_node.higher_siblings.to_a).to eq([last_node])
301
+ expect(last_node.lower_siblings.to_a).to eq([first_node, node(:second_root)])
302
+ end
303
+ end
304
+
305
+ describe "#move_to_bottom" do
306
+ it "should return true when attempting to move the last sibling" do
307
+ expect(node(:third_root).move_to_bottom).to eq(true)
308
+ expect(node(:second_child_of_first_root).move_to_bottom).to eq(true)
309
+ end
310
+
311
+ it "should be able to move the first sibling to the bottom" do
312
+ first_node = node(:first_root)
313
+ middle_node = node(:second_root)
314
+ last_node = node(:third_root)
315
+ first_node.move_to_bottom
316
+ middle_node.reload
317
+ last_node.reload
318
+ expect(first_node).not_to be_at_top
319
+ expect(first_node).to be_at_bottom
320
+ expect(last_node).not_to be_at_bottom
321
+ expect(last_node).not_to be_at_top
322
+ expect(middle_node).to be_at_top
323
+ expect(first_node.lower_siblings.to_a).to eq([])
324
+ expect(last_node.higher_siblings.to_a).to eq([middle_node])
325
+ end
326
+ end
327
+
328
+ describe "#move_up" do
329
+ it "should correctly move nodes up" do
330
+ node(:third).move_up
331
+ expect(node(:third_root).children).to eq([node(:first), node(:third), node(:second)])
332
+ end
333
+ end
334
+
335
+ describe "#move_down" do
336
+ it "should correctly move nodes down" do
337
+ node(:first).move_down
338
+ expect(node(:third_root).children).to eq([node(:second), node(:first), node(:third)])
339
+ end
340
+ end
341
+ end # moving nodes around
342
+ end # Mongoid::Tree::Ordering