mongoid-ancestry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,266 @@
1
+ require 'spec_helper'
2
+
3
+ describe MongoidAncestry do
4
+
5
+ subject { MongoidAncestry }
6
+
7
+ it "should be saved with unique id for new records" do
8
+ subject.with_model do |model|
9
+ new_record = model.create!
10
+ new_record.uid.should eql(1)
11
+ expect { new_record.save }.to_not change(new_record, :uid)
12
+ expect { new_record.save! }.to_not change(new_record, :uid)
13
+ model.create.uid.should eql(2)
14
+ model.create!.uid.should eql(3)
15
+ end
16
+ end
17
+
18
+ it "should raise error on non unique id" do
19
+ subject.with_model do |model|
20
+ model.create_indexes
21
+ expect do
22
+ first_record = model.new
23
+ first_record.stub!(:set_uid => true)
24
+ first_record.save
25
+ second = model.new
26
+ second.should_receive(:set_uid).exactly(3).times.and_return(true)
27
+ second.save
28
+ end.to raise_error(Mongo::OperationFailure, /duplicate key error/)
29
+ end
30
+ end
31
+
32
+ it "should have tree navigation" do
33
+ subject.with_model :depth => 3, :width => 3 do |model, roots|
34
+ roots.each do |lvl0_node, lvl0_children|
35
+ # Ancestors assertions
36
+ lvl0_node.ancestor_ids.should eql([])
37
+ lvl0_node.ancestors.to_a.should eql([])
38
+ lvl0_node.path_ids.should eql([lvl0_node.uid])
39
+ lvl0_node.path.to_a.should eql([lvl0_node])
40
+ lvl0_node.depth.should eql(0)
41
+ # Parent assertions
42
+ lvl0_node.parent_id.should be_nil
43
+ lvl0_node.parent.should be_nil
44
+ # Root assertions
45
+ lvl0_node.root_id.should eql(lvl0_node.uid)
46
+ lvl0_node.root.should eql(lvl0_node)
47
+ lvl0_node.is_root?.should be_true
48
+ # Children assertions
49
+ lvl0_node.child_ids.should eql(lvl0_children.map(&:first).map(&:uid))
50
+ lvl0_node.children.to_a.should eql(lvl0_children.map(&:first))
51
+ lvl0_node.has_children?.should be_true
52
+ lvl0_node.is_childless?.should be_false
53
+ # Siblings assertions
54
+ lvl0_node.sibling_ids.should eql(roots.map(&:first).map(&:uid))
55
+ lvl0_node.siblings.to_a.should eql(roots.map(&:first))
56
+ lvl0_node.has_siblings?.should be_true
57
+ lvl0_node.is_only_child?.should be_false
58
+ # Descendants assertions
59
+ descendants = model.all.find_all do |node|
60
+ node.ancestor_ids.include?(lvl0_node.uid)
61
+ end
62
+ lvl0_node.descendant_ids.should eql(descendants.map(&:uid))
63
+ lvl0_node.descendants.to_a.should eql(descendants)
64
+ lvl0_node.subtree.to_a.should eql([lvl0_node] + descendants)
65
+
66
+ lvl0_children.each do |lvl1_node, lvl1_children|
67
+ # Ancestors assertions
68
+ lvl1_node.ancestor_ids.should eql([lvl0_node.uid])
69
+ lvl1_node.ancestors.to_a.should eql([lvl0_node])
70
+ lvl1_node.path_ids.should eql([lvl0_node.uid, lvl1_node.uid])
71
+ lvl1_node.path.to_a.should eql([lvl0_node, lvl1_node])
72
+ lvl1_node.depth.should eql(1)
73
+ # Parent assertions
74
+ lvl1_node.parent_id.should eql(lvl0_node.uid)
75
+ lvl1_node.parent.should eql(lvl0_node)
76
+ # Root assertions
77
+ lvl1_node.root_id.should eql(lvl0_node.uid)
78
+ lvl1_node.root.should eql(lvl0_node)
79
+ lvl1_node.is_root?.should be_false
80
+ # Children assertions
81
+ lvl1_node.child_ids.should eql(lvl1_children.map(&:first).map(&:uid))
82
+ lvl1_node.children.to_a.should eql(lvl1_children.map(&:first))
83
+ lvl1_node.has_children?.should be_true
84
+ lvl1_node.is_childless?.should be_false
85
+ # Siblings assertions
86
+ lvl1_node.sibling_ids.should eql(lvl0_children.map(&:first).map(&:uid))
87
+ lvl1_node.siblings.to_a.should eql(lvl0_children.map(&:first))
88
+ lvl1_node.has_siblings?.should be_true
89
+ lvl1_node.is_only_child?.should be_false
90
+ # Descendants assertions
91
+ descendants = model.all.find_all do |node|
92
+ node.ancestor_ids.include? lvl1_node.uid
93
+ end
94
+
95
+ lvl1_node.descendant_ids.should eql(descendants.map(&:uid))
96
+ lvl1_node.descendants.to_a.should eql(descendants)
97
+ lvl1_node.subtree.to_a.should eql([lvl1_node] + descendants)
98
+
99
+ lvl1_children.each do |lvl2_node, lvl2_children|
100
+ # Ancestors assertions
101
+ lvl2_node.ancestor_ids.should eql([lvl0_node.uid, lvl1_node.uid])
102
+ lvl2_node.ancestors.to_a.should eql([lvl0_node, lvl1_node])
103
+ lvl2_node.path_ids.should eql([lvl0_node.uid, lvl1_node.uid, lvl2_node.uid])
104
+ lvl2_node.path.to_a.should eql([lvl0_node, lvl1_node, lvl2_node])
105
+ lvl2_node.depth.should eql(2)
106
+ # Parent assertions
107
+ lvl2_node.parent_id.should eql(lvl1_node.uid)
108
+ lvl2_node.parent.should eql(lvl1_node)
109
+ # Root assertions
110
+ lvl2_node.root_id.should eql(lvl0_node.uid)
111
+ lvl2_node.root.should eql(lvl0_node)
112
+ lvl2_node.is_root?.should be_false
113
+ # Children assertions
114
+ lvl2_node.child_ids.should eql([])
115
+ lvl2_node.children.to_a.should eql([])
116
+ lvl2_node.has_children?.should be_false
117
+ lvl2_node.is_childless?.should be_true
118
+ # Siblings assertions
119
+ lvl2_node.sibling_ids.should eql(lvl1_children.map(&:first).map(&:uid))
120
+ lvl2_node.siblings.to_a.should eql(lvl1_children.map(&:first))
121
+ lvl2_node.has_siblings?.should be_true
122
+ lvl2_node.is_only_child?.should be_false
123
+ # Descendants assertions
124
+ descendants = model.all.find_all do |node|
125
+ node.ancestor_ids.include? lvl2_node.id
126
+ end
127
+ lvl2_node.descendant_ids.should eql(descendants.map(&:uid))
128
+ lvl2_node.descendants.to_a.should eql(descendants)
129
+ lvl2_node.subtree.to_a.should eql([lvl2_node] + descendants)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ it "should validate ancestry field" do
137
+ subject.with_model do |model|
138
+ node = model.create
139
+ ['3', '10/2', '1/4/30', nil].each do |value|
140
+ node.send :write_attribute, model.ancestry_field, value
141
+ node.valid?
142
+ node.errors[model.ancestry_field].blank?.should be_true
143
+ end
144
+ ['1/3/', '/2/3', 'a', 'a/b', '-34', '/54'].each do |value|
145
+ node.send :write_attribute, model.ancestry_field, value
146
+ node.valid?
147
+ node.errors[model.ancestry_field].blank?.should be_false
148
+ end
149
+ end
150
+ end
151
+
152
+ it "should move descendants with node" do
153
+ subject.with_model :depth => 3, :width => 3 do |model, roots|
154
+ root1, root2, root3 = roots.map(&:first)
155
+
156
+ descendants = root1.descendants.asc(:uid).to_a
157
+ expect {
158
+ root1.parent = root2
159
+ root1.save!
160
+ root1.descendants.asc(:uid).to_a.should eql(descendants)
161
+ }.to change(root2.descendants, 'size').by(root1.subtree.size)
162
+
163
+ descendants = root2.descendants.asc(:uid).to_a
164
+ expect {
165
+ root2.parent = root3
166
+ root2.save!
167
+ root2.descendants.asc(:uid).to_a.should eql(descendants)
168
+ }.to change(root3.descendants, 'size').by(root2.subtree.size)
169
+
170
+ descendants = root1.descendants.asc(:uid).to_a
171
+ expect {
172
+ expect {
173
+ root1.parent = nil
174
+ root1.save!
175
+ root1.descendants.asc(:uid).to_a.should eql(descendants)
176
+ }.to change(root3.descendants, 'size').by(-root1.subtree.size)
177
+ }.to change(root2.descendants, 'size').by(-root1.subtree.size)
178
+ end
179
+ end
180
+
181
+ it "should validate ancestry exclude self" do
182
+ subject.with_model do |model|
183
+ parent = model.create!
184
+ child = parent.children.create
185
+ expect { parent.update_attributes! :parent => child }.to raise_error(Mongoid::Errors::Validations)
186
+ end
187
+ end
188
+
189
+ it "should have depth caching" do
190
+ subject.with_model :depth => 3, :width => 3, :cache_depth => true, :depth_cache_field => :depth_cache do |model, roots|
191
+ roots.each do |lvl0_node, lvl0_children|
192
+ lvl0_node.depth_cache.should eql(0)
193
+ lvl0_children.each do |lvl1_node, lvl1_children|
194
+ lvl1_node.depth_cache.should eql(1)
195
+ lvl1_children.each do |lvl2_node, lvl2_children|
196
+ lvl2_node.depth_cache.should eql(2)
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ it "should have descendants with depth constraints" do
204
+ subject.with_model :depth => 4, :width => 4, :cache_depth => true do |model, roots|
205
+ model.roots.first.descendants(:before_depth => 2).count.should eql(4)
206
+ model.roots.first.descendants(:to_depth => 2).count.should eql(20)
207
+ model.roots.first.descendants(:at_depth => 2).count.should eql(16)
208
+ model.roots.first.descendants(:from_depth => 2).count.should eql(80)
209
+ model.roots.first.descendants(:after_depth => 2).count.should eql(64)
210
+ end
211
+ end
212
+
213
+ it "should have subtree with depth constraints" do
214
+ subject.with_model :depth => 4, :width => 4, :cache_depth => true do |model, roots|
215
+ model.roots.first.subtree(:before_depth => 2).count.should eql(5)
216
+ model.roots.first.subtree(:to_depth => 2).count.should eql(21)
217
+ model.roots.first.subtree(:at_depth => 2).count.should eql(16)
218
+ model.roots.first.subtree(:from_depth => 2).count.should eql(80)
219
+ model.roots.first.subtree(:after_depth => 2).count.should eql(64)
220
+ end
221
+ end
222
+
223
+ it "should have ancestors with depth constraints" do
224
+ subject.with_model :cache_depth => true do |model|
225
+ node1 = model.create!
226
+ node2 = node1.children.create
227
+ node3 = node2.children.create
228
+ node4 = node3.children.create
229
+ node5 = node4.children.create
230
+ leaf = node5.children.create
231
+
232
+ leaf.ancestors(:before_depth => -2).to_a.should eql([node1, node2, node3])
233
+ leaf.ancestors(:to_depth => -2).to_a.should eql([node1, node2, node3, node4])
234
+ leaf.ancestors(:at_depth => -2).to_a.should eql([node4])
235
+ leaf.ancestors(:from_depth => -2).to_a.should eql([node4, node5])
236
+ leaf.ancestors(:after_depth => -2).to_a.should eql([node5])
237
+ end
238
+ end
239
+
240
+ it "should have path with depth constraints" do
241
+ subject.with_model :cache_depth => true do |model|
242
+ node1 = model.create!
243
+ node2 = node1.children.create
244
+ node3 = node2.children.create
245
+ node4 = node3.children.create
246
+ node5 = node4.children.create
247
+ leaf = node5.children.create
248
+
249
+ leaf.path(:before_depth => -2).to_a.should eql([node1, node2, node3])
250
+ leaf.path(:to_depth => -2).to_a.should eql([node1, node2, node3, node4])
251
+ leaf.path(:at_depth => -2).to_a.should eql([node4])
252
+ leaf.path(:from_depth => -2).to_a.should eql([node4, node5, leaf])
253
+ leaf.path(:after_depth => -2).to_a.should eql([node5, leaf])
254
+ end
255
+ end
256
+
257
+ it "should raise exception on unknown depth field" do
258
+ subject.with_model :cache_depth => true do |model|
259
+ expect {
260
+ model.create!.subtree(:this_is_not_a_valid_depth_option => 42)
261
+ }.to raise_error(Mongoid::Ancestry::Error)
262
+ end
263
+ end
264
+
265
+
266
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ require 'mongoid/ancestry/exceptions'
4
+
5
+
6
+ describe MongoidAncestry do
7
+
8
+ subject { MongoidAncestry }
9
+
10
+ it "should have ancestry fields" do
11
+ subject.with_model do |model|
12
+ model.fields['ancestry'].options[:type].should eql(String)
13
+ model.fields['uid'].options[:type].should eql(Integer)
14
+ end
15
+ end
16
+
17
+ it "should have ancestry indexes" do
18
+ subject.with_model do |model|
19
+ model.index_options.should have_key(:uid)
20
+ end
21
+ end
22
+
23
+ it "should have non default ancestry field" do
24
+ subject.with_model :ancestry_field => :alternative_ancestry do |model|
25
+ model.ancestry_field.should eql(:alternative_ancestry)
26
+ end
27
+ end
28
+
29
+ it "should set ancestry field" do
30
+ subject.with_model do |model|
31
+ model.ancestry_field = :ancestors
32
+ model.ancestry_field.should eql(:ancestors)
33
+ model.ancestry_field = :ancestry
34
+ model.ancestry_field.should eql(:ancestry)
35
+ end
36
+ end
37
+
38
+ it "should have default orphan strategy" do
39
+ subject.with_model do |model|
40
+ model.orphan_strategy.should eql(:destroy)
41
+ end
42
+ end
43
+
44
+ it "should have non default orphan strategy" do
45
+ subject.with_model :orphan_strategy => :rootify do |model|
46
+ model.orphan_strategy.should eql(:rootify)
47
+ end
48
+ end
49
+
50
+ it "should set orphan strategy" do
51
+ subject.with_model do |model|
52
+ model.orphan_strategy = :rootify
53
+ model.orphan_strategy.should eql(:rootify)
54
+ model.orphan_strategy = :destroy
55
+ model.orphan_strategy.should eql(:destroy)
56
+ end
57
+ end
58
+
59
+ it "should not set invalid orphan strategy" do
60
+ subject.with_model do |model|
61
+ expect {
62
+ model.orphan_strategy = :non_existent_orphan_strategy
63
+ }.to raise_error Mongoid::Ancestry::Error
64
+ end
65
+ end
66
+
67
+ it "should setup test nodes" do
68
+ subject.with_model :depth => 3, :width => 3 do |model, roots|
69
+ roots.class.should eql(Array)
70
+ roots.length.should eql(3)
71
+ roots.each do |node, children|
72
+ node.class.should eql(model)
73
+ children.class.should eql(Array)
74
+ children.length.should eql(3)
75
+ children.each do |node, children|
76
+ node.class.should eql(model)
77
+ children.class.should eql(Array)
78
+ children.length.should eql(3)
79
+ children.each do |node, children|
80
+ node.class.should eql(model)
81
+ children.class.should eql(Array)
82
+ children.length.should eql(0)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ it "should have STI support" do
90
+ subject.with_model :extra_columns => {:type => :string} do |model|
91
+ subclass1 = Object.const_set 'Subclass1', Class.new(model)
92
+ (class << subclass1; self; end).send(:define_method, :model_name) do
93
+ Struct.new(:human, :underscore).new 'Subclass1', 'subclass1'
94
+ end
95
+ subclass2 = Object.const_set 'Subclass2', Class.new(model)
96
+ (class << subclass2; self; end).send(:define_method, :model_name) do
97
+ Struct.new(:human, :underscore).new 'Subclass1', 'subclass1'
98
+ end
99
+
100
+ node1 = subclass1.create
101
+ node2 = subclass2.create :parent => node1
102
+ node3 = subclass1.create :parent => node2
103
+ node4 = subclass2.create :parent => node3
104
+ node5 = subclass1.create :parent => node4
105
+
106
+ model.all.each do |node|
107
+ [subclass1, subclass2].include?(node.class).should be_true
108
+ end
109
+
110
+ node1.descendants.map(&:id).should eql([node2.id, node3.id, node4.id, node5.id])
111
+ node1.subtree.map(&:id).should eql([node1.id, node2.id, node3.id, node4.id, node5.id])
112
+ node5.ancestors.map(&:id).should eql([node1.id, node2.id, node3.id, node4.id])
113
+ node5.path.map(&:id).should eql([node1.id, node2.id, node3.id, node4.id, node5.id])
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'mongoid'
5
+ require 'rspec'
6
+
7
+ Mongoid.configure do |config|
8
+ logger = Logger.new('log/test.log')
9
+ config.master = Mongo::Connection.new('localhost', 27017,
10
+ :logger => logger).db('ancestry_test')
11
+ config.logger = logger
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
15
+
16
+ RSpec.configure do |config|
17
+ config.after :each do
18
+ Mongoid.master.collections.reject { |c| c.name =~ /^system\./ }.each(&:drop)
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ require 'mongoid/ancestry'
2
+
3
+
4
+ class MongoidAncestry
5
+
6
+ def self.with_model options = {}
7
+ depth = options.delete(:depth) || 0
8
+ width = options.delete(:width) || 0
9
+ extra_columns = options.delete(:extra_columns)
10
+ skip_ancestry = options.delete(:skip_ancestry)
11
+
12
+ begin
13
+ model = Class.new
14
+ (class << model; self; end).send :define_method, :model_name do; Struct.new(:human, :underscore, :i18n_key).new 'TestNode', 'test_node', 'key'; end
15
+ const_set 'TestNode', model
16
+ TestNode.send(:include, Mongoid::Document)
17
+ TestNode.send(:include, Mongoid::Ancestry) unless skip_ancestry
18
+
19
+ extra_columns.each do |name, type|
20
+ TestNode.send :field, name, :type => type.to_s.capitalize.constantize
21
+ end unless extra_columns.nil?
22
+
23
+ TestNode.has_ancestry options unless skip_ancestry
24
+
25
+ if depth > 0
26
+ yield TestNode, create_test_nodes(TestNode, depth, width)
27
+ else
28
+ yield TestNode
29
+ end
30
+ ensure
31
+ #Mongoid.master.collections.reject { |c| c.name =~ /^system\./ }.each(&:drop)
32
+ remove_const "TestNode"
33
+ end
34
+ end
35
+
36
+ def self.create_test_nodes model, depth, width, parent = nil
37
+ unless depth == 0
38
+ Array.new width do
39
+ node = model.create!(:parent => parent)
40
+ [node, create_test_nodes(model, depth - 1, width, node)]
41
+ end
42
+ else; []; end
43
+ end
44
+ end