mongoid-ancestry 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,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