mongoid-ancestry-fixes 0.0.1 → 0.0.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.
- checksums.yaml +8 -8
- metadata +11 -34
- data/.gitignore +0 -42
- data/.travis.yml +0 -3
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -47
- data/Guardfile +0 -10
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -21
- data/init.rb +0 -1
- data/install.rb +0 -1
- data/lib/mongoid-ancestry.rb +0 -15
- data/lib/mongoid-ancestry/class_methods.rb +0 -212
- data/lib/mongoid-ancestry/exceptions.rb +0 -6
- data/lib/mongoid-ancestry/instance_methods.rb +0 -248
- data/lib/mongoid-ancestry/version.rb +0 -5
- data/log/.gitignore +0 -4
- data/mongoid-ancestry.gemspec +0 -29
- data/spec/lib/ancestry_spec.rb +0 -110
- data/spec/lib/mongoid-ancestry/class_methods_spec.rb +0 -300
- data/spec/lib/mongoid-ancestry/instance_methods_spec.rb +0 -241
- data/spec/spec_helper.rb +0 -22
- data/spec/support/models.rb +0 -40
@@ -1,248 +0,0 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module Ancestry
|
3
|
-
|
4
|
-
# Validate that the ancestors don't include itself
|
5
|
-
def ancestry_exclude_self
|
6
|
-
if ancestor_ids.include? id
|
7
|
-
errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.")
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# Update descendants with new ancestry
|
12
|
-
def update_descendants_with_new_ancestry
|
13
|
-
# Skip this if callbacks are disabled
|
14
|
-
unless ancestry_callbacks_disabled?
|
15
|
-
# If node is valid, not a new record and ancestry was updated ...
|
16
|
-
if changed.include?(self.base_class.ancestry_field.to_s) && !new_record? && valid?
|
17
|
-
# ... for each descendant ...
|
18
|
-
descendants.each do |descendant|
|
19
|
-
# ... replace old ancestry with new ancestry
|
20
|
-
descendant.without_ancestry_callbacks do
|
21
|
-
for_replace = \
|
22
|
-
if read_attribute(self.class.ancestry_field).blank?
|
23
|
-
id.to_s
|
24
|
-
else
|
25
|
-
"#{read_attribute self.class.ancestry_field}/#{id}"
|
26
|
-
end
|
27
|
-
new_ancestry = descendant.read_attribute(descendant.class.ancestry_field).gsub(/^#{self.child_ancestry}/, for_replace)
|
28
|
-
descendant.update_attribute(self.base_class.ancestry_field, new_ancestry)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Apply orphan strategy
|
36
|
-
def apply_orphan_strategy
|
37
|
-
# Skip this if callbacks are disabled
|
38
|
-
unless ancestry_callbacks_disabled?
|
39
|
-
# If this isn't a new record ...
|
40
|
-
unless new_record?
|
41
|
-
# ... make al children root if orphan strategy is rootify
|
42
|
-
if self.base_class.orphan_strategy == :rootify
|
43
|
-
descendants.each do |descendant|
|
44
|
-
descendant.without_ancestry_callbacks do
|
45
|
-
val = \
|
46
|
-
unless descendant.ancestry == child_ancestry
|
47
|
-
descendant.read_attribute(descendant.class.ancestry_field).gsub(/^#{child_ancestry}\//, '')
|
48
|
-
end
|
49
|
-
descendant.update_attribute descendant.class.ancestry_field, val
|
50
|
-
end
|
51
|
-
end
|
52
|
-
# ... destroy all descendants if orphan strategy is destroy
|
53
|
-
elsif self.base_class.orphan_strategy == :destroy
|
54
|
-
descendants.all.each do |descendant|
|
55
|
-
descendant.without_ancestry_callbacks { descendant.destroy }
|
56
|
-
end
|
57
|
-
# ... throw an exception if it has children and orphan strategy is restrict
|
58
|
-
elsif self.base_class.orphan_strategy == :restrict
|
59
|
-
raise Error.new('Cannot delete record because it has descendants.') unless is_childless?
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# The ancestry value for this record's children
|
66
|
-
def child_ancestry
|
67
|
-
# New records cannot have children
|
68
|
-
raise Error.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
|
69
|
-
|
70
|
-
if self.send("#{self.base_class.ancestry_field}_was").blank?
|
71
|
-
id.to_s
|
72
|
-
else
|
73
|
-
"#{self.send "#{self.base_class.ancestry_field}_was"}/#{id}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Scope
|
78
|
-
def current_search_scope
|
79
|
-
self.embedded? ? self._parent.send(self.base_class.to_s.tableize) : self.base_class
|
80
|
-
end
|
81
|
-
|
82
|
-
# Ancestors
|
83
|
-
def ancestor_ids
|
84
|
-
read_attribute(self.base_class.ancestry_field).to_s.split('/').map { |id| cast_primary_key(id) }
|
85
|
-
end
|
86
|
-
|
87
|
-
def ancestor_conditions
|
88
|
-
{ :_id.in => ancestor_ids }
|
89
|
-
end
|
90
|
-
|
91
|
-
def ancestors depth_options = {}
|
92
|
-
self.base_class.scope_depth(depth_options, depth).where(ancestor_conditions)
|
93
|
-
end
|
94
|
-
|
95
|
-
def path_ids
|
96
|
-
ancestor_ids + [id]
|
97
|
-
end
|
98
|
-
|
99
|
-
def path_conditions
|
100
|
-
{ :_id.in => path_ids }
|
101
|
-
end
|
102
|
-
|
103
|
-
def path depth_options = {}
|
104
|
-
self.base_class.scope_depth(depth_options, depth).where(path_conditions)
|
105
|
-
end
|
106
|
-
|
107
|
-
def depth
|
108
|
-
ancestor_ids.size
|
109
|
-
end
|
110
|
-
|
111
|
-
def cache_depth
|
112
|
-
write_attribute self.base_class.depth_cache_field, depth
|
113
|
-
end
|
114
|
-
|
115
|
-
# Parent
|
116
|
-
def parent= parent
|
117
|
-
write_attribute(self.base_class.ancestry_field, parent.blank? ? nil : parent.child_ancestry)
|
118
|
-
end
|
119
|
-
|
120
|
-
def parent_id= parent_id
|
121
|
-
self.parent = parent_id.blank? ? nil : current_search_scope.find(parent_id)
|
122
|
-
end
|
123
|
-
|
124
|
-
def parent_id
|
125
|
-
ancestor_ids.empty? ? nil : ancestor_ids.last
|
126
|
-
end
|
127
|
-
|
128
|
-
def parent
|
129
|
-
parent_id.blank? ? nil : current_search_scope.find(parent_id)
|
130
|
-
end
|
131
|
-
|
132
|
-
# Root
|
133
|
-
def root_id
|
134
|
-
(root_id == id) ? self : current_search.find(root_id)
|
135
|
-
end
|
136
|
-
|
137
|
-
def root
|
138
|
-
(root_id == id) ? self : current_search_scope.find(root_id)
|
139
|
-
end
|
140
|
-
|
141
|
-
def is_root?
|
142
|
-
read_attribute(self.base_class.ancestry_field).blank?
|
143
|
-
end
|
144
|
-
|
145
|
-
# Children
|
146
|
-
def child_conditions
|
147
|
-
{self.base_class.ancestry_field => child_ancestry}
|
148
|
-
end
|
149
|
-
|
150
|
-
def children
|
151
|
-
current_search_scope.where(child_conditions)
|
152
|
-
end
|
153
|
-
|
154
|
-
def child_ids
|
155
|
-
children.only(:_id).map(&:id)
|
156
|
-
end
|
157
|
-
|
158
|
-
def has_children?
|
159
|
-
self.children.present?
|
160
|
-
end
|
161
|
-
|
162
|
-
def is_childless?
|
163
|
-
!has_children?
|
164
|
-
end
|
165
|
-
|
166
|
-
# Siblings
|
167
|
-
def sibling_conditions
|
168
|
-
{self.base_class.ancestry_field => read_attribute(self.base_class.ancestry_field)}
|
169
|
-
end
|
170
|
-
|
171
|
-
def siblings
|
172
|
-
self.base_class.where sibling_conditions
|
173
|
-
end
|
174
|
-
|
175
|
-
def sibling_ids
|
176
|
-
siblings.only(:_id).map(&:id)
|
177
|
-
end
|
178
|
-
|
179
|
-
def has_siblings?
|
180
|
-
self.siblings.count > 1
|
181
|
-
end
|
182
|
-
|
183
|
-
def is_only_child?
|
184
|
-
!has_siblings?
|
185
|
-
end
|
186
|
-
|
187
|
-
# Descendants
|
188
|
-
def descendant_conditions
|
189
|
-
[
|
190
|
-
{ self.base_class.ancestry_field => /^#{child_ancestry}\// },
|
191
|
-
{ self.base_class.ancestry_field => child_ancestry }
|
192
|
-
]
|
193
|
-
end
|
194
|
-
|
195
|
-
def descendants depth_options = {}
|
196
|
-
self.base_class.scope_depth(depth_options, depth).any_of(descendant_conditions)
|
197
|
-
end
|
198
|
-
|
199
|
-
def descendant_ids depth_options = {}
|
200
|
-
descendants(depth_options).only(:_id).map(&:id)
|
201
|
-
end
|
202
|
-
|
203
|
-
# Subtree
|
204
|
-
def subtree_conditions
|
205
|
-
[
|
206
|
-
{ :_id => id },
|
207
|
-
{ self.base_class.ancestry_field => /^#{child_ancestry}\// },
|
208
|
-
{ self.base_class.ancestry_field => child_ancestry }
|
209
|
-
]
|
210
|
-
end
|
211
|
-
|
212
|
-
def subtree depth_options = {}
|
213
|
-
self.base_class.scope_depth(depth_options, depth).any_of(subtree_conditions)
|
214
|
-
end
|
215
|
-
|
216
|
-
def subtree_ids depth_options = {}
|
217
|
-
subtree(depth_options).only(:_id).map(&:id)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Callback disabling
|
221
|
-
def without_ancestry_callbacks
|
222
|
-
@disable_ancestry_callbacks = true
|
223
|
-
yield
|
224
|
-
@disable_ancestry_callbacks = false
|
225
|
-
end
|
226
|
-
|
227
|
-
def ancestry_callbacks_disabled?
|
228
|
-
!!@disable_ancestry_callbacks
|
229
|
-
end
|
230
|
-
|
231
|
-
private
|
232
|
-
|
233
|
-
def cast_primary_key(key)
|
234
|
-
if primary_key_type == Integer
|
235
|
-
key.to_i
|
236
|
-
elsif primary_key_type == BSON::ObjectId && key =~ /[a-z0-9]{24}/
|
237
|
-
BSON::ObjectId.convert(self, key)
|
238
|
-
else
|
239
|
-
key
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
def primary_key_type
|
244
|
-
@primary_key_type ||= self.base_class.fields['_id'].options[:type]
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
end
|
data/log/.gitignore
DELETED
data/mongoid-ancestry.gemspec
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "mongoid-ancestry/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = 'mongoid-ancestry-fixes'
|
7
|
-
s.version = '0.0.1'
|
8
|
-
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Stefan Kroes", "Anton Orel"]
|
10
|
-
s.email = ["eagle.anton@gmail.com"]
|
11
|
-
s.description = %q{Organise Mongoid model into a tree structure}
|
12
|
-
s.homepage = "http://github.com/skyeagle/mongoid-ancestry"
|
13
|
-
s.summary = %q{Ancestry allows the records of a Mongoid model to be organised in a tree structure, using a single, intuitively formatted database field. It exposes all the standard tree structure relations (ancestors, parent, root, children, siblings, descendants) and all of them can be fetched in a single query. Additional features are named_scopes, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.}
|
14
|
-
s.licenses = ["MIT"]
|
15
|
-
|
16
|
-
s.rubyforge_project = "mongoid-ancestry-fixes"
|
17
|
-
|
18
|
-
s.files = `git ls-files`.split("\n")
|
19
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
-
s.require_paths = ["lib"]
|
22
|
-
s.extra_rdoc_files = [
|
23
|
-
"README.md"
|
24
|
-
]
|
25
|
-
|
26
|
-
s.add_dependency('mongoid', ">= 2.0")
|
27
|
-
s.add_dependency('bson_ext', ">= 1.3")
|
28
|
-
end
|
29
|
-
|
data/spec/lib/ancestry_spec.rb
DELETED
@@ -1,110 +0,0 @@
|
|
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
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should have non default ancestry field" do
|
17
|
-
subject.with_model :ancestry_field => :alternative_ancestry do |model|
|
18
|
-
model.ancestry_field.should eql(:alternative_ancestry)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should set ancestry field" do
|
23
|
-
subject.with_model do |model|
|
24
|
-
model.ancestry_field = :ancestors
|
25
|
-
model.ancestry_field.should eql(:ancestors)
|
26
|
-
model.ancestry_field = :ancestry
|
27
|
-
model.ancestry_field.should eql(:ancestry)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should have default orphan strategy" do
|
32
|
-
subject.with_model do |model|
|
33
|
-
model.orphan_strategy.should eql(:destroy)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should have non default orphan strategy" do
|
38
|
-
subject.with_model :orphan_strategy => :rootify do |model|
|
39
|
-
model.orphan_strategy.should eql(:rootify)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should set orphan strategy" do
|
44
|
-
subject.with_model do |model|
|
45
|
-
model.orphan_strategy = :rootify
|
46
|
-
model.orphan_strategy.should eql(:rootify)
|
47
|
-
model.orphan_strategy = :destroy
|
48
|
-
model.orphan_strategy.should eql(:destroy)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should not set invalid orphan strategy" do
|
53
|
-
subject.with_model do |model|
|
54
|
-
expect {
|
55
|
-
model.orphan_strategy = :non_existent_orphan_strategy
|
56
|
-
}.to raise_error Mongoid::Ancestry::Error
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should setup test nodes" do
|
61
|
-
subject.with_model :depth => 3, :width => 3 do |model, roots|
|
62
|
-
roots.class.should eql(Array)
|
63
|
-
roots.length.should eql(3)
|
64
|
-
roots.each do |node, children|
|
65
|
-
node.class.should eql(model)
|
66
|
-
children.class.should eql(Array)
|
67
|
-
children.length.should eql(3)
|
68
|
-
children.each do |node, children|
|
69
|
-
node.class.should eql(model)
|
70
|
-
children.class.should eql(Array)
|
71
|
-
children.length.should eql(3)
|
72
|
-
children.each do |node, children|
|
73
|
-
node.class.should eql(model)
|
74
|
-
children.class.should eql(Array)
|
75
|
-
children.length.should eql(0)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
it "should have STI support" do
|
83
|
-
subject.with_model :extra_columns => {:type => :string} do |model|
|
84
|
-
subclass1 = Object.const_set 'Subclass1', Class.new(model)
|
85
|
-
(class << subclass1; self; end).send(:define_method, :model_name) do
|
86
|
-
Struct.new(:human, :underscore).new 'Subclass1', 'subclass1'
|
87
|
-
end
|
88
|
-
subclass2 = Object.const_set 'Subclass2', Class.new(model)
|
89
|
-
(class << subclass2; self; end).send(:define_method, :model_name) do
|
90
|
-
Struct.new(:human, :underscore).new 'Subclass1', 'subclass1'
|
91
|
-
end
|
92
|
-
|
93
|
-
node1 = subclass1.create
|
94
|
-
node2 = subclass2.create :parent => node1
|
95
|
-
node3 = subclass1.create :parent => node2
|
96
|
-
node4 = subclass2.create :parent => node3
|
97
|
-
node5 = subclass1.create :parent => node4
|
98
|
-
|
99
|
-
model.all.each do |node|
|
100
|
-
[subclass1, subclass2].include?(node.class).should be_true
|
101
|
-
end
|
102
|
-
|
103
|
-
node1.descendants.map(&:id).should eql([node2.id, node3.id, node4.id, node5.id])
|
104
|
-
node1.subtree.map(&:id).should eql([node1.id, node2.id, node3.id, node4.id, node5.id])
|
105
|
-
node5.ancestors.map(&:id).should eql([node1.id, node2.id, node3.id, node4.id])
|
106
|
-
node5.path.map(&:id).should eql([node1.id, node2.id, node3.id, node4.id, node5.id])
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
@@ -1,300 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe MongoidAncestry do
|
4
|
-
|
5
|
-
subject { MongoidAncestry }
|
6
|
-
|
7
|
-
it "should have scopes" do
|
8
|
-
subject.with_model :depth => 3, :width => 3 do |model, roots|
|
9
|
-
# Roots assertion
|
10
|
-
model.roots.all.to_a.should eql(roots.map(&:first))
|
11
|
-
|
12
|
-
model.all.each do |test_node|
|
13
|
-
# Assertions for ancestors_of named scope
|
14
|
-
model.ancestors_of(test_node).all.should == test_node.ancestors.all
|
15
|
-
model.ancestors_of(test_node.id).all.to_a.should eql(test_node.ancestors.all.to_a)
|
16
|
-
# Assertions for children_of named scope
|
17
|
-
model.children_of(test_node).all.to_a.should eql(test_node.children.all.to_a)
|
18
|
-
model.children_of(test_node.id).all.to_a.should eql(test_node.children.all.to_a)
|
19
|
-
# Assertions for descendants_of named scope
|
20
|
-
model.descendants_of(test_node).all.should == (test_node.descendants.all)
|
21
|
-
model.descendants_of(test_node.id).all.to_a.should eql(test_node.descendants.all.to_a)
|
22
|
-
# Assertions for subtree_of named scope
|
23
|
-
model.subtree_of(test_node).all.to_a.should eql(test_node.subtree.all.to_a)
|
24
|
-
model.subtree_of(test_node.id).all.to_a.should eql(test_node.subtree.all.to_a)
|
25
|
-
# Assertions for siblings_of named scope
|
26
|
-
model.siblings_of(test_node).all.to_a.should eql(test_node.siblings.all.to_a)
|
27
|
-
model.siblings_of(test_node.id).all.to_a.should eql(test_node.siblings.all.to_a)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should be arranged" do
|
33
|
-
subject.with_model :depth => 3, :width => 3 do |model, roots|
|
34
|
-
id_sorter = Proc.new {|a, b|; a.to_param <=> b.to_param }
|
35
|
-
arranged_nodes = model.arrange
|
36
|
-
arranged_nodes.size.should eql(3)
|
37
|
-
arranged_nodes.each do |node, children|
|
38
|
-
children.keys.sort(&id_sorter).should eql(node.children.sort(&id_sorter))
|
39
|
-
children.each do |node, children|
|
40
|
-
children.keys.sort(&id_sorter).should eql(node.children.sort(&id_sorter))
|
41
|
-
children.each do |node, children|
|
42
|
-
children.size.should eql(0)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should have arrange order option" do
|
50
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
51
|
-
descending_nodes_lvl0 = model.arrange :order => [:_id, :desc]
|
52
|
-
ascending_nodes_lvl0 = model.arrange :order => [:_id, :asc]
|
53
|
-
|
54
|
-
descending_nodes_lvl0.keys.zip(ascending_nodes_lvl0.keys.reverse).each do |descending_node, ascending_node|
|
55
|
-
ascending_node.should eql(descending_node)
|
56
|
-
descending_nodes_lvl1 = descending_nodes_lvl0[descending_node]
|
57
|
-
ascending_nodes_lvl1 = ascending_nodes_lvl0[ascending_node]
|
58
|
-
descending_nodes_lvl1.keys.zip(ascending_nodes_lvl1.keys.reverse).each do |descending_node, ascending_node|
|
59
|
-
ascending_node.should eql(descending_node)
|
60
|
-
descending_nodes_lvl2 = descending_nodes_lvl1[descending_node]
|
61
|
-
ascending_nodes_lvl2 = ascending_nodes_lvl1[ascending_node]
|
62
|
-
descending_nodes_lvl2.keys.zip(ascending_nodes_lvl2.keys.reverse).each do |descending_node, ascending_node|
|
63
|
-
ascending_node.should eql(descending_node)
|
64
|
-
descending_nodes_lvl3 = descending_nodes_lvl2[descending_node]
|
65
|
-
ascending_nodes_lvl3 = ascending_nodes_lvl2[ascending_node]
|
66
|
-
descending_nodes_lvl3.keys.zip(ascending_nodes_lvl3.keys.reverse).each do |descending_node, ascending_node|
|
67
|
-
ascending_node.should eql(descending_node)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
it "should have valid orphan rootify strategy" do
|
76
|
-
subject.with_model :depth => 3, :width => 3 do |model, roots|
|
77
|
-
model.orphan_strategy = :rootify
|
78
|
-
root = roots.first.first
|
79
|
-
children = root.children.all
|
80
|
-
root.destroy
|
81
|
-
children.each do |child|
|
82
|
-
child.reload
|
83
|
-
child.is_root?.should be_true
|
84
|
-
child.children.size.should eql(3)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
it "should have valid orphan destroy strategy" do
|
90
|
-
subject.with_model :depth => 3, :width => 3 do |model, roots|
|
91
|
-
model.orphan_strategy = :destroy
|
92
|
-
root = roots.first.first
|
93
|
-
expect { root.destroy }.to change(model, :count).by(-root.subtree.size)
|
94
|
-
node = model.roots.first.children.first
|
95
|
-
expect { node.destroy }.to change(model, :count).by(-node.subtree.size)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
it "should have valid orphan restrict strategy" do
|
100
|
-
subject.with_model :depth => 3, :width => 3 do |model, roots|
|
101
|
-
model.orphan_strategy = :restrict
|
102
|
-
root = roots.first.first
|
103
|
-
expect { root.destroy }.to raise_error Mongoid::Ancestry::Error
|
104
|
-
expect { root.children.first.children.first.destroy }.to_not raise_error Mongoid::Ancestry::Error
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
it "should check that there are no errors on a valid tree" do
|
109
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
110
|
-
expect { model.check_ancestry_integrity! }.to_not raise_error(Mongoid::Ancestry::Error)
|
111
|
-
model.check_ancestry_integrity!(:report => :list).size.should eql(0)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
it "should check detection of invalid format for ancestry field" do
|
116
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
117
|
-
roots.first.first.update_attribute model.ancestry_field, 'invalid_ancestry'
|
118
|
-
expect { model.check_ancestry_integrity! }.to raise_error(Mongoid::Ancestry::IntegrityError)
|
119
|
-
model.check_ancestry_integrity!(:report => :list).size.should eql(1)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
it "should check detection of non-existent ancestor" do
|
124
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
125
|
-
node = roots.first.first
|
126
|
-
node.without_ancestry_callbacks do
|
127
|
-
node.update_attribute model.ancestry_field, 35
|
128
|
-
end
|
129
|
-
expect { model.check_ancestry_integrity! }.to raise_error(Mongoid::Ancestry::IntegrityError)
|
130
|
-
model.check_ancestry_integrity!(:report => :list).size.should eql(1)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
it "should check detection of cyclic ancestry" do
|
135
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
136
|
-
node = roots.first.first
|
137
|
-
node.update_attribute model.ancestry_field, node.id
|
138
|
-
expect { model.check_ancestry_integrity! }.to raise_error(Mongoid::Ancestry::IntegrityError)
|
139
|
-
model.check_ancestry_integrity!(:report => :list).size.should eql(1)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
it "should check detection of conflicting parent id" do
|
144
|
-
subject.with_model do |model|
|
145
|
-
model.destroy_all
|
146
|
-
model.create!(model.ancestry_field => model.create!(model.ancestry_field => model.create!(model.ancestry_field => nil).id).id)
|
147
|
-
expect { model.check_ancestry_integrity! }.to raise_error(Mongoid::Ancestry::IntegrityError)
|
148
|
-
model.check_ancestry_integrity!(:report => :list).size.should eql(1)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def assert_integrity_restoration model
|
153
|
-
expect { model.check_ancestry_integrity! }.to raise_error(Mongoid::Ancestry::IntegrityError)
|
154
|
-
model.restore_ancestry_integrity!
|
155
|
-
expect { model.check_ancestry_integrity! }.to_not raise_error(Mongoid::Ancestry::IntegrityError)
|
156
|
-
end
|
157
|
-
|
158
|
-
it "should check that integrity is restored for invalid format for ancestry field" do
|
159
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
160
|
-
roots.first.first.update_attribute model.ancestry_field, 'invalid_ancestry'
|
161
|
-
assert_integrity_restoration model
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
it "should check that integrity is restored for non-existent ancestor" do
|
166
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
167
|
-
roots.first.first.update_attribute model.ancestry_field, 35
|
168
|
-
assert_integrity_restoration model
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
it "should check that integrity is restored for cyclic ancestry" do
|
173
|
-
subject.with_model :width => 3, :depth => 3 do |model, roots|
|
174
|
-
node = roots.first.first
|
175
|
-
node.update_attribute model.ancestry_field, node.id
|
176
|
-
assert_integrity_restoration model
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
it "should check that integrity is restored for conflicting parent id" do
|
181
|
-
subject.with_model do |model|
|
182
|
-
model.destroy_all
|
183
|
-
model.create!(model.ancestry_field => model.create!(model.ancestry_field => model.create!(model.ancestry_field => nil).id).id)
|
184
|
-
assert_integrity_restoration model
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
it "should create node through scope" do
|
189
|
-
subject.with_model do |model|
|
190
|
-
node = model.create!
|
191
|
-
child = node.children.create # doesn't pass with .create!
|
192
|
-
child.parent.should eql(node)
|
193
|
-
|
194
|
-
other_child = child.siblings.create # doesn't pass with .create!
|
195
|
-
other_child.parent.should eql(node)
|
196
|
-
|
197
|
-
grandchild = model.children_of(child).build # doesn't pass with .new
|
198
|
-
grandchild.save
|
199
|
-
grandchild.parent.should eql(child)
|
200
|
-
|
201
|
-
other_grandchild = model.siblings_of(grandchild).build # doesn't pass with .new
|
202
|
-
other_grandchild.save!
|
203
|
-
other_grandchild.parent.should eql(child)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
it "should have depth scopes" do
|
208
|
-
subject.with_model :depth => 4, :width => 2, :cache_depth => true do |model, roots|
|
209
|
-
model.before_depth(2).all? { |node| node.depth < 2 }.should be_true
|
210
|
-
model.to_depth(2).all? { |node| node.depth <= 2 }.should be_true
|
211
|
-
model.at_depth(2).all? { |node| node.depth == 2 }.should be_true
|
212
|
-
model.from_depth(2).all? { |node| node.depth >= 2 }.should be_true
|
213
|
-
model.after_depth(2).all? { |node| node.depth > 2 }.should be_true
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
it "should raise error on invalid scopes" do
|
218
|
-
subject.with_model do |model|
|
219
|
-
expect { model.before_depth(1) } .to raise_error(Mongoid::Ancestry::Error)
|
220
|
-
expect { model.to_depth(1) } .to raise_error(Mongoid::Ancestry::Error)
|
221
|
-
expect { model.at_depth(1) } .to raise_error(Mongoid::Ancestry::Error)
|
222
|
-
expect { model.from_depth(1) } .to raise_error(Mongoid::Ancestry::Error)
|
223
|
-
expect { model.after_depth(1) } .to raise_error(Mongoid::Ancestry::Error)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
it "should raise error on invalid has_ancestry options" do
|
228
|
-
subject.with_model do |model|
|
229
|
-
expect { model.has_ancestry :this_option_doesnt_exist => 42 }.to raise_error(Mongoid::Ancestry::Error)
|
230
|
-
expect { model.has_ancestry :not_a_hash }.to raise_error(Mongoid::Ancestry::Error)
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
it "should build ancestry from parent ids" do
|
235
|
-
subject.with_model :skip_ancestry => true, :extra_columns => {:parent_id => :integer} do |model|
|
236
|
-
[model.create!].each do |parent1|
|
237
|
-
(Array.new(5) { model.create :parent_id => parent1.id }).each do |parent2|
|
238
|
-
(Array.new(5) { model.create :parent_id => parent2.id }).each do |parent3|
|
239
|
-
(Array.new(5) { model.create :parent_id => parent3.id })
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
# Assert all nodes where created
|
245
|
-
model.count.should eql((0..3).map { |n| 5 ** n }.sum)
|
246
|
-
end
|
247
|
-
|
248
|
-
subject.with_model do |model|
|
249
|
-
|
250
|
-
model.build_ancestry_from_parent_ids!
|
251
|
-
|
252
|
-
# Assert ancestry integrity
|
253
|
-
model.check_ancestry_integrity!
|
254
|
-
|
255
|
-
roots = model.roots.all
|
256
|
-
## Assert single root node
|
257
|
-
roots.size.should eql(1)
|
258
|
-
|
259
|
-
## Assert it has 5 children
|
260
|
-
roots.each do |parent|
|
261
|
-
parent.children.count.should eql(5)
|
262
|
-
parent.children.each do |parent|
|
263
|
-
parent.children.count.should eql(5)
|
264
|
-
parent.children.each do |parent|
|
265
|
-
parent.children.count.should eql(5)
|
266
|
-
parent.children.each do |parent|
|
267
|
-
parent.children.count.should eql(0)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
it "should rebuild depth cache" do
|
276
|
-
subject.with_model :depth => 3, :width => 3, :cache_depth => true, :depth_cache_field => :depth_cache do |model, roots|
|
277
|
-
model.update_all(:depth_cache => nil)
|
278
|
-
|
279
|
-
# Assert cache was emptied correctly
|
280
|
-
model.all.each do |test_node|
|
281
|
-
test_node.depth_cache.should eql(nil)
|
282
|
-
end
|
283
|
-
|
284
|
-
# Rebuild cache
|
285
|
-
model.rebuild_depth_cache!
|
286
|
-
|
287
|
-
# Assert cache was rebuild correctly
|
288
|
-
model.all.each do |test_node|
|
289
|
-
test_node.depth_cache.should eql(test_node.depth)
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
it "should raise exception when rebuilding depth cache for model without depth caching" do
|
295
|
-
subject.with_model do |model|
|
296
|
-
expect { model.rebuild_depth_cache! }.to raise_error(Mongoid::Ancestry::Error)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
end
|