mongoid-tree-rational 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,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