acts_as_dag 1.2.6 → 2.0.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.
- checksums.yaml +4 -4
- data/README.md +139 -0
- data/lib/acts_as_dag.rb +2 -1
- data/lib/acts_as_dag/acts_as_dag.rb +132 -181
- data/lib/acts_as_dag/deprecated.rb +121 -0
- data/spec/acts_as_dag_spec.rb +730 -206
- data/spec/deprecated_spec.rb +128 -0
- data/spec/spec_helper.rb +3 -1
- metadata +21 -5
- data/README.rdoc +0 -28
@@ -0,0 +1,121 @@
|
|
1
|
+
module ActsAsDAG
|
2
|
+
module Deprecated
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
# Reorganizes the entire class of records based on their name, first resetting the hierarchy, then reoganizing
|
6
|
+
# Can pass a list of categories and only those will be reorganized
|
7
|
+
def reorganize(categories_to_reorganize = self.all)
|
8
|
+
puts "This method is deprecated and will be removed in a future version"
|
9
|
+
|
10
|
+
return if categories_to_reorganize.empty?
|
11
|
+
|
12
|
+
reset_hierarchy(categories_to_reorganize)
|
13
|
+
|
14
|
+
word_count_groups = categories_to_reorganize.group_by{|category| ActsAsDAG::Deprecated::HelperMethods.word_count(category)}.sort
|
15
|
+
roots_categories = word_count_groups.first[1].dup.sort_by(&:name) # We will build up a list of plinko targets, we start with the group of categories with the shortest word count
|
16
|
+
|
17
|
+
# Now plinko the next shortest word group into those targets
|
18
|
+
# If we can't plinko one, then it gets added as a root
|
19
|
+
word_count_groups[1..-1].each do |word_count, categories|
|
20
|
+
categories_with_no_parents = []
|
21
|
+
|
22
|
+
# Try drop each category into each root
|
23
|
+
categories.sort_by(&:name).each do |category|
|
24
|
+
ActiveRecord::Base.benchmark "Analyze #{category.name}" do
|
25
|
+
suitable_parent = false
|
26
|
+
roots_categories.each do |root|
|
27
|
+
suitable_parent = true if ActsAsDAG::Deprecated::HelperMethods.plinko(root, category)
|
28
|
+
end
|
29
|
+
unless suitable_parent
|
30
|
+
ActiveRecord::Base.logger.info { "Plinko couldn't find a suitable parent for #{category.name}" }
|
31
|
+
categories_with_no_parents << category
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add all categories from this group without suitable parents to the roots
|
37
|
+
if categories_with_no_parents.present?
|
38
|
+
ActiveRecord::Base.logger.info { "Adding #{categories_with_no_parents.collect(&:name).join(', ')} to roots" }
|
39
|
+
roots_categories.concat categories_with_no_parents
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module InstanceMethods
|
46
|
+
end
|
47
|
+
|
48
|
+
module HelperMethods
|
49
|
+
# Searches the subtree for the best parent for the other
|
50
|
+
# i.e. it lets you drop the category in at the top and it drops down the list until it finds its final resting place
|
51
|
+
def self.plinko(current, other)
|
52
|
+
# ActiveRecord::Base.logger.info { "Plinkoing '#{other.name}' into '#{current.name}'..." }
|
53
|
+
if should_descend_from?(current, other)
|
54
|
+
# Find the subtree of the current category that +other+ should descend from
|
55
|
+
subtree_other_should_descend_from = current.subtree.select{|record| should_descend_from?(record, other) }
|
56
|
+
# Of those, find the categories with the most number of matching words and make +other+ their child
|
57
|
+
# We find all suitable candidates to provide support for categories whose names are permutations of each other
|
58
|
+
# e.g. 'goat wool fibre' should be a child of 'goat wool' and 'wool goat' if both are present under 'goat'
|
59
|
+
new_parents_group = subtree_other_should_descend_from.group_by{|category| matching_word_count(other, category)}.sort.reverse.first
|
60
|
+
if new_parents_group.present?
|
61
|
+
for new_parent in new_parents_group[1]
|
62
|
+
ActiveRecord::Base.logger.info { " '#{other.name}' landed under '#{new_parent.name}'" }
|
63
|
+
other.add_parent(new_parent)
|
64
|
+
|
65
|
+
# We've just affected the associations in ways we can not possibly imagine, so let's clear the association cache
|
66
|
+
current.clear_association_cache
|
67
|
+
end
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convenience method for plinkoing multiple categories
|
74
|
+
# Plinko's multiple categories from shortest to longest in order to prevent the need for reorganization
|
75
|
+
def self.plinko_multiple(current, others)
|
76
|
+
groups = others.group_by{|category| word_count(category)}.sort
|
77
|
+
groups.each do |word_count, categories|
|
78
|
+
categories.each do |category|
|
79
|
+
unless plinko(current, category)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the portion of this category's name that is not present in any of it's parents
|
86
|
+
def self.unique_name_portion(current)
|
87
|
+
unique_portion = current.name.split
|
88
|
+
for parent in current.parents
|
89
|
+
for word in parent.name.split
|
90
|
+
unique_portion.delete(word)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
return unique_portion.empty? ? nil : unique_portion.join(' ')
|
95
|
+
end
|
96
|
+
|
97
|
+
# Checks if other should descend from +current+ based on name matching
|
98
|
+
# Returns true if other contains all the words from +current+, but has words that are not contained in +current+
|
99
|
+
def self.should_descend_from?(current, other)
|
100
|
+
return false if current == other
|
101
|
+
|
102
|
+
other_words = other.name.split
|
103
|
+
current_words = current.name.split
|
104
|
+
|
105
|
+
# (other contains all the words from current and more) && (current contains no words that are not also in other)
|
106
|
+
return (other_words - (current_words & other_words)).count > 0 && (current_words - other_words).count == 0
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.word_count(current)
|
110
|
+
current.name.split.count
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.matching_word_count(current, other)
|
114
|
+
other_words = other.name.split
|
115
|
+
self_words = current.name.split
|
116
|
+
return (other_words & self_words).count
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/spec/acts_as_dag_spec.rb
CHANGED
@@ -1,314 +1,838 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'acts_as_dag' do
|
4
|
+
before do
|
5
|
+
klass.destroy_all # Because we're using sqlite3 and it doesn't support transactional specs (afaik)
|
6
|
+
end
|
7
|
+
|
4
8
|
shared_examples_for "DAG Model" do
|
5
|
-
|
6
|
-
|
9
|
+
let (:grandpa) { klass.create(:name => 'grandpa') }
|
10
|
+
let (:dad) { klass.create(:name => 'dad') }
|
11
|
+
let (:mom) { klass.create(:name => 'mom') }
|
12
|
+
let (:suzy) { klass.create(:name => 'suzy') }
|
13
|
+
let (:billy) { klass.create(:name => 'billy') }
|
14
|
+
|
15
|
+
describe '#children' do
|
16
|
+
it "returns an ActiveRecord::Relation" do
|
17
|
+
expect(mom.children).to be_an(ActiveRecord::Relation)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "includes all children of the receiver" do
|
21
|
+
mom.add_child(suzy, billy)
|
22
|
+
expect(mom.children).to include(suzy,billy)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "doesn't include any records that are not children of the receiver" do
|
26
|
+
grandpa.add_child(mom)
|
27
|
+
expect(mom.children).not_to include(grandpa)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns records in the order they were added to the graph" do
|
31
|
+
grandpa.add_child(mom, dad)
|
32
|
+
expect(grandpa.children).to eq([mom, dad])
|
33
|
+
end
|
7
34
|
end
|
8
35
|
|
9
|
-
describe
|
10
|
-
|
11
|
-
|
12
|
-
@dad = @klass.create(:name => 'dad')
|
13
|
-
@mom = @klass.create(:name => 'mom')
|
14
|
-
@child = @klass.create(:name => 'child')
|
36
|
+
describe '#parents' do
|
37
|
+
it "returns an ActiveRecord::Relation" do
|
38
|
+
expect(mom.parents).to be_an(ActiveRecord::Relation)
|
15
39
|
end
|
16
40
|
|
17
|
-
it "
|
18
|
-
|
41
|
+
it "includes all parents of the receiver" do
|
42
|
+
suzy.add_parent(mom, dad)
|
43
|
+
expect(suzy.parents).to include(mom, dad)
|
19
44
|
end
|
20
45
|
|
21
|
-
it "
|
22
|
-
|
46
|
+
it "doesn't include any records that are not parents of the receiver" do
|
47
|
+
dad.add_parent(grandpa)
|
48
|
+
suzy.add_parent(mom, dad)
|
49
|
+
expect(suzy.parents).not_to include(grandpa)
|
23
50
|
end
|
24
51
|
|
25
|
-
it "
|
26
|
-
|
52
|
+
it "returns records in the order they were added to the graph" do
|
53
|
+
suzy.add_parent(mom, dad)
|
54
|
+
expect(suzy.parents).to eq([mom, dad])
|
55
|
+
end
|
56
|
+
end
|
27
57
|
|
28
|
-
|
58
|
+
describe '#descendants' do
|
59
|
+
it "returns an ActiveRecord::Relation" do
|
60
|
+
expect(mom.descendants).to be_an(ActiveRecord::Relation)
|
29
61
|
end
|
30
62
|
|
31
|
-
it "
|
32
|
-
|
63
|
+
it "doesn't include self" do
|
64
|
+
expect(mom.descendants).not_to include(mom)
|
65
|
+
end
|
33
66
|
|
34
|
-
|
67
|
+
it "includes all descendants of the receiver" do
|
68
|
+
grandpa.add_child(mom, dad)
|
69
|
+
mom.add_child(suzy)
|
70
|
+
dad.add_child(billy)
|
71
|
+
expect(grandpa.descendants).to include(mom, dad, suzy, billy)
|
35
72
|
end
|
36
73
|
|
37
|
-
it "
|
38
|
-
|
39
|
-
|
74
|
+
it "doesn't include any ancestors of the receiver" do
|
75
|
+
grandpa.add_child(mom)
|
76
|
+
mom.add_child(suzy)
|
77
|
+
expect(mom.descendants).not_to include(grandpa)
|
78
|
+
end
|
40
79
|
|
41
|
-
|
80
|
+
it "returns records in ascending order of distance, and ascending order added to graph" do
|
81
|
+
grandpa.add_child(mom, dad)
|
82
|
+
mom.add_child(suzy)
|
83
|
+
dad.add_child(billy)
|
84
|
+
expect(grandpa.descendants).to eq([mom, dad, suzy, billy])
|
42
85
|
end
|
43
86
|
|
44
|
-
it "
|
45
|
-
|
46
|
-
|
87
|
+
it "returns no duplicates when there are multiple paths to the same descendant" do
|
88
|
+
grandpa.add_child(mom, dad)
|
89
|
+
billy.add_parent(mom, dad)
|
90
|
+
|
91
|
+
expect(grandpa.descendants).to eq(grandpa.descendants.uniq)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#subtree' do
|
96
|
+
it "returns an ActiveRecord::Relation" do
|
97
|
+
expect(mom.subtree).to be_an(ActiveRecord::Relation)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "includes self" do
|
101
|
+
expect(mom.subtree).to include(mom)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "includes all descendants of the receiver" do
|
105
|
+
grandpa.add_child(mom)
|
106
|
+
mom.add_child(billy)
|
107
|
+
expect(grandpa.subtree).to include(mom, billy)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "doesn't include any ancestors of the receiver" do
|
111
|
+
grandpa.add_child(mom)
|
112
|
+
mom.add_child(billy)
|
113
|
+
expect(mom.subtree).not_to include(grandpa)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns records in ascending order of distance, and ascending order added to graph" do
|
117
|
+
grandpa.add_child(mom)
|
118
|
+
grandpa.add_child(dad)
|
119
|
+
mom.add_child(billy)
|
120
|
+
expect(grandpa.subtree).to eq([grandpa, mom, dad, billy])
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns no duplicates when there are multiple paths to the same descendant" do
|
124
|
+
grandpa.add_child(mom, dad)
|
125
|
+
billy.add_parent(mom, dad)
|
126
|
+
|
127
|
+
expect(grandpa.subtree).to eq(grandpa.subtree.uniq)
|
128
|
+
end
|
129
|
+
end
|
47
130
|
|
48
|
-
|
131
|
+
describe '#ancestors' do
|
132
|
+
it "returns an ActiveRecord::Relation" do
|
133
|
+
expect(mom.ancestors).to be_an(ActiveRecord::Relation)
|
49
134
|
end
|
50
135
|
|
51
|
-
it "
|
52
|
-
|
53
|
-
|
136
|
+
it "doesn't include self" do
|
137
|
+
expect(mom.ancestors).not_to include(mom)
|
138
|
+
end
|
54
139
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
140
|
+
it "includes all ancestors of the receiver" do
|
141
|
+
grandpa.add_child(mom)
|
142
|
+
mom.add_child(billy)
|
143
|
+
expect(billy.ancestors).to include(grandpa, mom)
|
59
144
|
end
|
60
145
|
|
61
|
-
it "
|
62
|
-
|
63
|
-
|
146
|
+
it "doesn't include any descendants of the receiver" do
|
147
|
+
grandpa.add_child(mom)
|
148
|
+
mom.add_child(billy)
|
149
|
+
expect(mom.ancestors).not_to include(billy)
|
150
|
+
end
|
64
151
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
152
|
+
it "returns records in descending order of distance, and ascending order added to graph" do
|
153
|
+
grandpa.add_child(mom)
|
154
|
+
mom.add_child(billy)
|
155
|
+
dad.add_child(billy)
|
156
|
+
expect(billy.ancestors).to eq([grandpa, mom, dad])
|
69
157
|
end
|
70
158
|
|
71
|
-
it "
|
72
|
-
|
73
|
-
|
159
|
+
it "returns no duplicates when there are multiple paths to the same ancestor" do
|
160
|
+
grandpa.add_child(mom, dad)
|
161
|
+
billy.add_parent(mom, dad)
|
74
162
|
|
75
|
-
|
163
|
+
expect(billy.ancestors).to eq(billy.ancestors.uniq)
|
76
164
|
end
|
165
|
+
end
|
77
166
|
|
78
|
-
|
79
|
-
|
80
|
-
|
167
|
+
describe '#path' do
|
168
|
+
it "returns an ActiveRecord::Relation" do
|
169
|
+
expect(mom.path).to be_an(ActiveRecord::Relation)
|
170
|
+
end
|
81
171
|
|
82
|
-
|
172
|
+
it "includes self" do
|
173
|
+
expect(mom.path).to include(mom)
|
83
174
|
end
|
84
175
|
|
85
|
-
it "
|
86
|
-
|
87
|
-
|
176
|
+
it "includes all ancestors of the receiver" do
|
177
|
+
grandpa.add_child(mom)
|
178
|
+
mom.add_child(billy)
|
179
|
+
dad.add_child(billy)
|
180
|
+
expect(billy.path).to include(grandpa, mom, dad)
|
181
|
+
end
|
88
182
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
183
|
+
it "doesn't include any descendants of the receiver" do
|
184
|
+
grandpa.add_child(mom)
|
185
|
+
mom.add_child(billy)
|
186
|
+
expect(mom.path).not_to include(billy)
|
93
187
|
end
|
94
188
|
|
95
|
-
it "
|
96
|
-
|
97
|
-
|
189
|
+
it "returns records in descending order of distance, and ascending order added to graph" do
|
190
|
+
grandpa.add_child(mom)
|
191
|
+
mom.add_child(billy)
|
192
|
+
dad.add_child(billy)
|
193
|
+
expect(billy.path).to eq([grandpa, mom, dad, billy])
|
98
194
|
end
|
99
195
|
|
100
|
-
it "
|
101
|
-
|
102
|
-
|
103
|
-
|
196
|
+
it "returns no duplicates when there are multiple paths to the same ancestor" do
|
197
|
+
grandpa.add_child(mom, dad)
|
198
|
+
billy.add_parent(mom, dad)
|
199
|
+
|
200
|
+
expect(billy.path).to eq(billy.path.uniq)
|
104
201
|
end
|
105
202
|
end
|
106
203
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
204
|
+
describe '#add_child' do
|
205
|
+
it "makes the record a child of the receiver" do
|
206
|
+
mom.add_child(billy)
|
207
|
+
expect(billy.child_of?(mom)).to be_truthy
|
208
|
+
end
|
112
209
|
|
113
|
-
|
114
|
-
|
210
|
+
it "makes the record a descendant of the receiver" do
|
211
|
+
mom.add_child(billy)
|
212
|
+
expect(billy.descendant_of?(mom)).to be_truthy
|
115
213
|
end
|
116
214
|
|
117
|
-
it "
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@mom.parent_links.should be_empty
|
122
|
-
@mom.child_links.should be_empty
|
215
|
+
it "makes the record an descendant of any of the receiver's ancestors" do
|
216
|
+
grandpa.add_child(mom)
|
217
|
+
mom.add_child(billy)
|
218
|
+
expect(billy.descendant_of?(grandpa)).to be_truthy
|
123
219
|
end
|
124
220
|
|
125
|
-
it "
|
126
|
-
|
221
|
+
it "can be called multiple times to add additional children" do
|
222
|
+
mom.add_child(suzy)
|
223
|
+
mom.add_child(billy)
|
224
|
+
expect(mom.children).to include(suzy, billy)
|
225
|
+
end
|
127
226
|
|
128
|
-
|
129
|
-
|
130
|
-
|
227
|
+
it "accepts multiple arguments, adding each as a child" do
|
228
|
+
mom.add_child(suzy, billy)
|
229
|
+
expect(mom.children).to include(suzy, billy)
|
131
230
|
end
|
132
231
|
|
133
|
-
it "
|
134
|
-
|
232
|
+
it "accepts an array of records, adding each as a child" do
|
233
|
+
mom.add_child([suzy, billy])
|
234
|
+
expect(mom.children).to include(suzy, billy)
|
135
235
|
end
|
136
236
|
end
|
137
237
|
|
138
|
-
describe
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
@big_totem_pole = @klass.create(:name => "big totem pole")
|
143
|
-
@big_model_totem_pole = @klass.create(:name => "big model totem pole")
|
144
|
-
@big_red_model_totem_pole = @klass.create(:name => "big red model totem pole")
|
238
|
+
describe '#add_parent' do
|
239
|
+
it "makes the record a parent of the receiver" do
|
240
|
+
suzy.add_parent(dad)
|
241
|
+
expect(dad.parent_of?(suzy)).to be_truthy
|
145
242
|
end
|
146
243
|
|
147
|
-
it "
|
148
|
-
|
149
|
-
|
150
|
-
@big_totem_pole.children.should == []
|
151
|
-
@big_totem_pole.ancestors.should == [@big_totem_pole]
|
152
|
-
@big_totem_pole.descendants.should == [@big_totem_pole]
|
244
|
+
it "makes the record a ancestor of the receiver" do
|
245
|
+
suzy.add_parent(dad)
|
246
|
+
expect(dad.ancestor_of?(suzy)).to be_truthy
|
153
247
|
end
|
154
248
|
|
155
|
-
it "
|
156
|
-
|
157
|
-
|
249
|
+
it "makes the record an ancestor of any of the receiver's ancestors" do
|
250
|
+
dad.add_parent(grandpa)
|
251
|
+
suzy.add_parent(dad)
|
252
|
+
expect(grandpa.ancestor_of?(suzy)).to be_truthy
|
158
253
|
end
|
159
254
|
|
160
|
-
it "
|
161
|
-
|
255
|
+
it "can be called multiple times to add additional parents" do
|
256
|
+
suzy.add_parent(mom)
|
257
|
+
suzy.add_parent(dad)
|
258
|
+
expect(suzy.parents).to include(mom, dad)
|
162
259
|
end
|
163
260
|
|
164
|
-
it "
|
165
|
-
|
261
|
+
it "accepts multiple arguments, adding each as a parent" do
|
262
|
+
suzy.add_parent(mom, dad)
|
263
|
+
expect(suzy.parents).to include(mom, dad)
|
264
|
+
end
|
166
265
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
@big_model_totem_pole.children.should == [@big_red_model_totem_pole]
|
266
|
+
it "accepts an array of records, adding each as a parent" do
|
267
|
+
suzy.add_parent([mom, dad])
|
268
|
+
expect(suzy.parents).to include(mom, dad)
|
171
269
|
end
|
270
|
+
end
|
172
271
|
|
173
|
-
|
174
|
-
|
272
|
+
describe '#ancestor_of?' do
|
273
|
+
it "returns true if the record is a ancestor of the receiver" do
|
274
|
+
grandpa.add_child(mom)
|
275
|
+
mom.add_child(billy)
|
276
|
+
expect(grandpa.ancestor_of?(billy)).to be_truthy
|
277
|
+
end
|
175
278
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
279
|
+
it "returns false if the record is not an ancestor of the receiver" do
|
280
|
+
grandpa.add_child(dad)
|
281
|
+
mom.add_child(billy)
|
282
|
+
expect(grandpa.ancestor_of?(billy)).to be_falsey
|
180
283
|
end
|
284
|
+
end
|
181
285
|
|
182
|
-
|
183
|
-
|
286
|
+
describe '#descendant_of?' do
|
287
|
+
it "returns true if the record is a descendant of the receiver" do
|
288
|
+
grandpa.add_child(mom)
|
289
|
+
mom.add_child(billy)
|
290
|
+
expect(billy.descendant_of?(grandpa)).to be_truthy
|
291
|
+
end
|
184
292
|
|
185
|
-
|
293
|
+
it "returns false if the record is not an descendant of the receiver" do
|
294
|
+
grandpa.add_child(dad)
|
295
|
+
mom.add_child(billy)
|
296
|
+
expect(billy.descendant_of?(grandpa)).to be_falsey
|
297
|
+
end
|
298
|
+
end
|
186
299
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
300
|
+
describe '#child_of?' do
|
301
|
+
it "returns true if the record is a child of the receiver" do
|
302
|
+
mom.add_child(billy)
|
303
|
+
expect(billy.child_of?(mom)).to be_truthy
|
191
304
|
end
|
192
305
|
|
193
|
-
it "
|
194
|
-
|
306
|
+
it "returns false if the record is not an child of the receiver" do
|
307
|
+
mom.add_child(suzy)
|
308
|
+
expect(billy.child_of?(mom)).to be_falsey
|
309
|
+
end
|
310
|
+
end
|
195
311
|
|
196
|
-
|
312
|
+
describe '#parent_of?' do
|
313
|
+
it "returns true if the record is a parent of the receiver" do
|
314
|
+
mom.add_child(billy)
|
315
|
+
expect(mom.parent_of?(billy)).to be_truthy
|
316
|
+
end
|
197
317
|
|
198
|
-
|
199
|
-
|
200
|
-
(
|
201
|
-
@big_model_totem_pole.reload.children.should == [@big_red_model_totem_pole]
|
202
|
-
@big_totem_pole_model.reload.children.should == [@big_red_model_totem_pole]
|
318
|
+
it "returns false if the record is not an parent of the receiver" do
|
319
|
+
mom.add_child(billy)
|
320
|
+
expect(mom.parent_of?(suzy)).to be_falsey
|
203
321
|
end
|
322
|
+
end
|
204
323
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
@big_model_totem_pole.add_child(@big_red_model_totem_pole)
|
211
|
-
end
|
324
|
+
describe '#root?' do
|
325
|
+
it "returns true if the record has no parents" do
|
326
|
+
mom.add_child(suzy)
|
327
|
+
expect(mom.root?).to be_truthy
|
328
|
+
end
|
212
329
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
# Totem Pole
|
217
|
-
# *|* \
|
218
|
-
# *|* Big Totem Pole
|
219
|
-
# *|* /
|
220
|
-
# Big Model Totem Pole
|
221
|
-
# |
|
222
|
-
# Big Red Model Totem Pole
|
223
|
-
#
|
224
|
-
before(:each) do
|
225
|
-
@totem_pole.add_child(@big_model_totem_pole)
|
226
|
-
end
|
227
|
-
|
228
|
-
it "should return multiple instances of descendants before breaking the old link" do
|
229
|
-
@totem.descendants.sort_by(&:id).should == [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
230
|
-
end
|
231
|
-
|
232
|
-
it "should return the correct inheritance chain after breaking the old link" do
|
233
|
-
@totem_pole.remove_child(@big_model_totem_pole)
|
234
|
-
|
235
|
-
@totem_pole.children.sort_by(&:id).should == [@big_totem_pole].sort_by(&:id)
|
236
|
-
@totem.descendants.sort_by(&:id).should == [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
237
|
-
end
|
238
|
-
|
239
|
-
it "should return the correct inheritance chain after breaking the old link when there is are two ancestor root nodes" do
|
240
|
-
pole = @klass.create(:name => "pole")
|
241
|
-
@totem_pole.add_parent(pole)
|
242
|
-
@totem_pole.remove_child(@big_model_totem_pole)
|
243
|
-
|
244
|
-
pole.descendants.sort_by(&:id).should == [pole, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
245
|
-
@totem_pole.children.sort_by(&:id).should == [@big_totem_pole].sort_by(&:id)
|
246
|
-
@totem.descendants.sort_by(&:id).should == [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
247
|
-
end
|
248
|
-
end
|
330
|
+
it "returns false if the record has parents" do
|
331
|
+
mom.add_parent(grandpa)
|
332
|
+
expect(mom.root?).to be_falsey
|
249
333
|
end
|
250
334
|
end
|
251
335
|
|
252
|
-
describe
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
@child = @klass.create(:name => 'child')
|
336
|
+
describe '#leaf?' do
|
337
|
+
it "returns true if the record has no children" do
|
338
|
+
mom.add_parent(grandpa)
|
339
|
+
expect(mom.leaf?).to be_truthy
|
340
|
+
end
|
258
341
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
@child.add_parent(@mom)
|
263
|
-
@mom.add_parent(@grandpa)
|
342
|
+
it "returns false if the record has children" do
|
343
|
+
mom.add_child(suzy)
|
344
|
+
expect(mom.leaf?).to be_falsey
|
264
345
|
end
|
346
|
+
end
|
265
347
|
|
266
|
-
|
267
|
-
|
348
|
+
describe '#make_root' do
|
349
|
+
it "makes the receiver a root node" do
|
350
|
+
mom.add_parent(grandpa)
|
351
|
+
mom.make_root
|
352
|
+
expect(mom.root?).to be_truthy
|
268
353
|
end
|
269
354
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
355
|
+
it "removes the receiver from the children of its parents" do
|
356
|
+
suzy.add_parent(mom, dad)
|
357
|
+
suzy.make_root
|
358
|
+
expect(mom.children).not_to include(suzy)
|
359
|
+
expect(dad.children).not_to include(suzy)
|
360
|
+
end
|
275
361
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
362
|
+
it "doesn't modify the relationship between the receiver and its descendants" do
|
363
|
+
mom.add_parent(grandpa)
|
364
|
+
mom.add_child(suzy, billy)
|
365
|
+
mom.make_root
|
366
|
+
expect(mom.children).to eq([suzy, billy])
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
describe '#lineage' do
|
371
|
+
it "returns an ActiveRecord::Relation" do
|
372
|
+
expect(mom.children).to be_an(ActiveRecord::Relation)
|
373
|
+
end
|
374
|
+
|
375
|
+
it "doesn't include the receiver" do
|
376
|
+
expect(mom.lineage).not_to include(mom)
|
377
|
+
end
|
378
|
+
|
379
|
+
it "includes all ancestors and descendants of the receiver" do
|
380
|
+
mom.add_child(suzy, billy)
|
381
|
+
mom.add_parent(grandpa)
|
382
|
+
expect(mom.lineage).to include(grandpa, suzy, billy)
|
383
|
+
end
|
384
|
+
|
385
|
+
it "return ancestors and descendants of the receiver in the order they would be if called separately" do
|
386
|
+
mom.add_child(suzy, billy)
|
387
|
+
mom.add_parent(grandpa)
|
388
|
+
expect(mom.lineage).to eq([grandpa, suzy, billy])
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
describe '::children' do
|
393
|
+
it "returns an ActiveRecord::Relation" do
|
394
|
+
expect(klass.children).to be_an(ActiveRecord::Relation)
|
395
|
+
end
|
396
|
+
|
397
|
+
it "returns records that have at least 1 parent" do
|
398
|
+
mom.add_parent(grandpa)
|
399
|
+
mom.add_child(suzy)
|
400
|
+
expect(klass.children).to include(mom, suzy)
|
401
|
+
end
|
402
|
+
|
403
|
+
it "doesn't returns records without parents" do
|
404
|
+
mom.add_parent(grandpa)
|
405
|
+
mom.add_child(suzy)
|
406
|
+
expect(klass.children).not_to include(grandpa)
|
407
|
+
end
|
408
|
+
|
409
|
+
it "does not return duplicate records, regardless of the number of parents" do
|
410
|
+
suzy.add_parent(mom, dad)
|
411
|
+
expect(klass.children).to eq([suzy])
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
describe '::parent_records' do
|
416
|
+
it "returns an ActiveRecord::Relation" do
|
417
|
+
expect(klass.parent_records).to be_an(ActiveRecord::Relation)
|
418
|
+
end
|
419
|
+
|
420
|
+
it "returns records that have at least 1 child" do
|
421
|
+
mom.add_parent(grandpa)
|
422
|
+
mom.add_child(suzy)
|
423
|
+
expect(klass.parent_records).to include(grandpa, mom)
|
424
|
+
end
|
425
|
+
|
426
|
+
it "doesn't returns records without children" do
|
427
|
+
mom.add_parent(grandpa)
|
428
|
+
mom.add_child(suzy)
|
429
|
+
expect(klass.parent_records).not_to include(suzy)
|
430
|
+
end
|
431
|
+
|
432
|
+
it "does not return duplicate records, regardless of the number of children" do
|
433
|
+
mom.add_child(suzy, billy)
|
434
|
+
expect(klass.parent_records).to eq([mom])
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
describe '#ancestors_of' do
|
439
|
+
it "returns an ActiveRecord::Relation" do
|
440
|
+
expect(klass.ancestors_of(suzy)).to be_an(ActiveRecord::Relation)
|
441
|
+
end
|
442
|
+
|
443
|
+
it "doesn't include the given record" do
|
444
|
+
expect(klass.ancestors_of(suzy)).not_to include(suzy)
|
445
|
+
end
|
446
|
+
|
447
|
+
it "returns records that are ancestors of the given record" do
|
448
|
+
suzy.add_parent(mom, dad)
|
449
|
+
expect(klass.ancestors_of(suzy)).to include(mom, dad)
|
450
|
+
end
|
451
|
+
|
452
|
+
it "doesn't return records that are not ancestors of the given record" do
|
453
|
+
suzy.add_parent(mom)
|
454
|
+
expect(klass.ancestors_of(suzy)).not_to include(dad)
|
455
|
+
end
|
456
|
+
|
457
|
+
it "returns records that are ancestors of the given record id" do
|
458
|
+
suzy.add_parent(mom, dad)
|
459
|
+
expect(klass.ancestors_of(suzy.id)).to include(mom, dad)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
describe '#descendants_of' do
|
464
|
+
it "returns an ActiveRecord::Relation" do
|
465
|
+
expect(klass.descendants_of(grandpa)).to be_an(ActiveRecord::Relation)
|
466
|
+
end
|
467
|
+
|
468
|
+
it "doesn't include the given record" do
|
469
|
+
expect(klass.descendants_of(grandpa)).not_to include(grandpa)
|
470
|
+
end
|
471
|
+
|
472
|
+
it "returns records that are descendants of the given record" do
|
473
|
+
grandpa.add_child(mom, dad)
|
474
|
+
expect(klass.descendants_of(grandpa)).to include(mom, dad)
|
475
|
+
end
|
476
|
+
|
477
|
+
it "doesn't return records that are not descendants of the given record" do
|
478
|
+
grandpa.add_child(mom)
|
479
|
+
expect(klass.descendants_of(grandpa)).not_to include(dad)
|
480
|
+
end
|
481
|
+
|
482
|
+
it "returns records that are descendants of the given record id" do
|
483
|
+
grandpa.add_child(mom, dad)
|
484
|
+
expect(klass.descendants_of(grandpa.id)).to include(mom, dad)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
describe '#path_of' do
|
489
|
+
it "returns an ActiveRecord::Relation" do
|
490
|
+
expect(klass.path_of(suzy)).to be_an(ActiveRecord::Relation)
|
491
|
+
end
|
492
|
+
|
493
|
+
it "returns records that are path-members of the given record" do
|
494
|
+
suzy.add_parent(mom, dad)
|
495
|
+
expect(klass.path_of(suzy)).to include(mom, dad, suzy)
|
496
|
+
end
|
497
|
+
|
498
|
+
it "doesn't return records that are not path-members of the given record" do
|
499
|
+
suzy.add_parent(mom)
|
500
|
+
expect(klass.path_of(suzy)).not_to include(dad)
|
501
|
+
end
|
502
|
+
|
503
|
+
it "returns records that are path-members of the given record id" do
|
504
|
+
suzy.add_parent(mom, dad)
|
505
|
+
expect(klass.path_of(suzy.id)).to include(mom, dad, suzy)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
describe '#subtree_of' do
|
510
|
+
it "returns an ActiveRecord::Relation" do
|
511
|
+
expect(klass.subtree_of(grandpa)).to be_an(ActiveRecord::Relation)
|
512
|
+
end
|
513
|
+
|
514
|
+
it "returns records that are subtree-members of the given record" do
|
515
|
+
grandpa.add_child(mom, dad)
|
516
|
+
expect(klass.subtree_of(grandpa)).to include(grandpa, mom, dad)
|
517
|
+
end
|
518
|
+
|
519
|
+
it "doesn't return records that are not subtree-members of the given record" do
|
520
|
+
grandpa.add_child(mom)
|
521
|
+
expect(klass.subtree_of(grandpa)).not_to include(dad)
|
522
|
+
end
|
523
|
+
|
524
|
+
it "returns records that are subtree-members of the given record id" do
|
525
|
+
grandpa.add_child(mom, dad)
|
526
|
+
expect(klass.subtree_of(grandpa.id)).to include(grandpa, mom, dad)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
describe '#destroy' do
|
531
|
+
it "destroys associated hierarchy-tracking records" do
|
532
|
+
mom.add_parent(grandpa)
|
533
|
+
mom.add_child(suzy)
|
534
|
+
|
535
|
+
mom.destroy
|
536
|
+
|
537
|
+
expect(mom.ancestor_links).to contain_exactly
|
538
|
+
expect(mom.path_links).to contain_exactly
|
539
|
+
expect(mom.parent_links).to contain_exactly
|
540
|
+
expect(mom.child_links).to contain_exactly
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
describe '#parents=' do
|
545
|
+
before { suzy.parents = [mom, dad] }
|
546
|
+
|
547
|
+
it "sets the receiver's parents to the given array" do
|
548
|
+
expect(suzy.parents).to eq([mom, dad])
|
549
|
+
end
|
550
|
+
|
551
|
+
it "updates the ancestors of the receiver" do
|
552
|
+
expect(suzy.ancestors).to eq([mom, dad])
|
553
|
+
end
|
554
|
+
|
555
|
+
it "unsets the receiver's parents when given an empty array" do
|
556
|
+
suzy.parents = []
|
557
|
+
expect(suzy.parents).to contain_exactly
|
558
|
+
end
|
559
|
+
|
560
|
+
it "updates the ancestors of the receivers when given an empty array" do
|
561
|
+
suzy.parents = []
|
562
|
+
expect(suzy.ancestors).to contain_exactly
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
describe '#children=' do
|
567
|
+
before { grandpa.children = [mom, dad] }
|
568
|
+
|
569
|
+
it "sets the receiver's children to the given array" do
|
570
|
+
expect(grandpa.children).to eq([mom, dad])
|
571
|
+
end
|
572
|
+
|
573
|
+
it "updates the descendants of the receiver" do
|
574
|
+
expect(grandpa.descendants).to eq([mom, dad])
|
575
|
+
end
|
576
|
+
|
577
|
+
it "unsets the receiver's children when given an empty array" do
|
578
|
+
grandpa.children = []
|
579
|
+
expect(grandpa.children).to contain_exactly
|
580
|
+
end
|
581
|
+
|
582
|
+
it "updates the descendants of the receivers when given an empty array" do
|
583
|
+
grandpa.children = []
|
584
|
+
expect(grandpa.descendants).to contain_exactly
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
describe '#parent_ids=' do
|
589
|
+
before { suzy.parent_ids = [mom.id, dad.id] }
|
590
|
+
|
591
|
+
it "sets the receiver's parents to the given array" do
|
592
|
+
expect(suzy.parents).to eq([mom, dad])
|
593
|
+
end
|
594
|
+
|
595
|
+
it "updates the ancestors of the receiver" do
|
596
|
+
expect(suzy.ancestors).to eq([mom, dad])
|
597
|
+
end
|
598
|
+
|
599
|
+
it "unsets the receiver's parents when given an empty array" do
|
600
|
+
suzy.parents = []
|
601
|
+
expect(suzy.parents).to contain_exactly
|
602
|
+
end
|
603
|
+
|
604
|
+
it "updates the ancestors of the receivers when given an empty array" do
|
605
|
+
suzy.parents = []
|
606
|
+
expect(suzy.ancestors).to contain_exactly
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
describe '#child_ids=' do
|
611
|
+
before { grandpa.child_ids = [mom.id, dad.id] }
|
612
|
+
|
613
|
+
it "sets the receiver's children to the given array" do
|
614
|
+
expect(grandpa.children).to eq([mom, dad])
|
615
|
+
end
|
616
|
+
|
617
|
+
it "updates the descendants of the receiver" do
|
618
|
+
expect(grandpa.descendants).to eq([mom, dad])
|
619
|
+
end
|
620
|
+
|
621
|
+
it "unsets the receiver's children when given an empty array" do
|
622
|
+
grandpa.children = []
|
623
|
+
expect(grandpa.children).to contain_exactly
|
624
|
+
end
|
625
|
+
|
626
|
+
it "updates the descendants of the receivers when given an empty array" do
|
627
|
+
grandpa.children = []
|
628
|
+
expect(grandpa.descendants).to contain_exactly
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
describe '#create' do
|
633
|
+
it "sets the receiver's children to the given array" do
|
634
|
+
record = klass.create!(:children => [mom, dad])
|
635
|
+
expect(record.children).to contain_exactly(mom, dad)
|
636
|
+
end
|
637
|
+
|
638
|
+
it "updates the descendants of the receiver" do
|
639
|
+
record = klass.create!(:children => [mom, dad])
|
640
|
+
record.reload
|
641
|
+
expect(record.descendants).to contain_exactly(mom, dad)
|
642
|
+
end
|
643
|
+
|
644
|
+
it "sets the receiver's parents to the given array" do
|
645
|
+
record = klass.create!(:parents => [mom, dad])
|
646
|
+
expect(record.parents).to contain_exactly(mom, dad)
|
647
|
+
end
|
648
|
+
|
649
|
+
it "updates the ancestors of the receiver" do
|
650
|
+
record = klass.create!(:parents => [mom, dad])
|
651
|
+
record.reload
|
652
|
+
expect(record.ancestors).to contain_exactly(mom, dad)
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
describe '::reset_hierarchy' do
|
657
|
+
it "reinitialize links and descendants after resetting the hierarchy" do
|
658
|
+
mom.add_parent(grandpa)
|
659
|
+
mom.add_child(suzy)
|
660
|
+
|
661
|
+
klass.reset_hierarchy
|
662
|
+
expect(mom.parents).to contain_exactly()
|
663
|
+
expect(mom.children).to contain_exactly()
|
664
|
+
expect(mom.path).to contain_exactly(mom)
|
665
|
+
expect(mom.subtree).to contain_exactly(mom)
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
describe '#ancestor_links' do
|
670
|
+
it "doesn't include a link to the receiver" do
|
671
|
+
expect(mom.ancestor_links).to contain_exactly
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
describe '#path_links' do
|
676
|
+
it "includes a link to the receiver" do
|
677
|
+
expect(mom.path_links.first.descendant).to eq(mom)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
describe '#descendant_links' do
|
682
|
+
it "doesn't include a link to the receiver" do
|
683
|
+
expect(mom.descendant_links).to contain_exactly
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
describe '#subtree_links' do
|
688
|
+
it "includes a link to the receiver" do
|
689
|
+
expect(mom.subtree_links.first.descendant).to eq(mom)
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
describe '::roots' do
|
694
|
+
it "returns all root nodes" do
|
695
|
+
mom; dad
|
696
|
+
expect(klass.roots).to include(mom, dad)
|
697
|
+
end
|
698
|
+
|
699
|
+
it "doesn't return non-root nodes" do
|
700
|
+
mom.add_child(suzy)
|
701
|
+
expect(klass.roots).not_to include(suzy)
|
702
|
+
end
|
703
|
+
|
704
|
+
it "doesn't mark returned records as readonly" do
|
705
|
+
mom; dad
|
706
|
+
expect(klass.roots.none?(&:readonly?)).to be_truthy
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
describe '::leafs' do
|
711
|
+
it "returns all leaf nodes" do
|
712
|
+
mom.add_child(suzy)
|
713
|
+
expect(klass.leafs).to include(suzy)
|
714
|
+
end
|
715
|
+
|
716
|
+
it "doesn't return non-leaf nodes" do
|
717
|
+
mom.add_child(suzy)
|
718
|
+
expect(klass.leafs).not_to include(mom)
|
719
|
+
end
|
720
|
+
|
721
|
+
it "doesn't mark returned records as readonly" do
|
722
|
+
mom.add_child(suzy)
|
723
|
+
expect(klass.leafs.none?(&:readonly?)).to be_truthy
|
724
|
+
end
|
725
|
+
end
|
726
|
+
|
727
|
+
describe '::children' do
|
728
|
+
it "returns all child nodes" do
|
729
|
+
mom.add_child(suzy)
|
730
|
+
expect(klass.children).to contain_exactly(suzy)
|
731
|
+
end
|
732
|
+
|
733
|
+
it "doesn't return non-child nodes" do
|
734
|
+
mom; dad
|
735
|
+
expect(klass.children).not_to include(mom, dad)
|
736
|
+
end
|
737
|
+
|
738
|
+
it "doesn't mark returned records as readonly" do
|
739
|
+
mom.add_child(suzy)
|
740
|
+
expect(klass.children.none?(&:readonly?)).to be_truthy
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
describe '::parent_records' do
|
745
|
+
it "returns all parent nodes" do
|
746
|
+
mom.add_child(suzy)
|
747
|
+
expect(klass.parent_records).to contain_exactly(mom)
|
748
|
+
end
|
749
|
+
|
750
|
+
it "doesn't return non-parent nodes" do
|
751
|
+
mom; dad
|
752
|
+
expect(klass.parent_records).not_to include(mom, dad)
|
753
|
+
end
|
754
|
+
|
755
|
+
it "doesn't mark returned records as readonly" do
|
756
|
+
mom.add_child(suzy)
|
757
|
+
expect(klass.parent_records.none?(&:readonly?)).to be_truthy
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
context "When two paths of the same length exist to the same node and a link between parent and ancestor is removed" do
|
762
|
+
before do
|
763
|
+
grandpa.add_child(mom, dad)
|
764
|
+
suzy.add_parent(mom, dad)
|
765
|
+
end
|
766
|
+
|
767
|
+
describe '#remove_parent' do
|
768
|
+
it "updates the ancestor links correctly" do
|
769
|
+
dad.remove_parent(grandpa)
|
770
|
+
expect(suzy.ancestors).to contain_exactly(grandpa, dad, mom)
|
771
|
+
expect(mom.ancestors).to contain_exactly(grandpa)
|
772
|
+
expect(dad.ancestors).to contain_exactly()
|
280
773
|
end
|
281
774
|
|
282
|
-
it "
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
775
|
+
it "updates the descendant links correctly" do
|
776
|
+
dad.remove_parent(grandpa)
|
777
|
+
expect(suzy.descendants).to contain_exactly()
|
778
|
+
expect(mom.descendants).to contain_exactly(suzy)
|
779
|
+
expect(dad.descendants).to contain_exactly(suzy)
|
780
|
+
expect(grandpa.descendants).to contain_exactly(mom, suzy)
|
287
781
|
end
|
288
782
|
end
|
289
783
|
end
|
784
|
+
|
785
|
+
# describe "Includes, Eager-Loads, and Preloads" do
|
786
|
+
# before(:each) do
|
787
|
+
# dad.add_parent(grandpa)
|
788
|
+
# billy.add_parent(dad, mom)
|
789
|
+
# end
|
790
|
+
|
791
|
+
# it "should preload path in the correct order" do
|
792
|
+
# records = klass.order("#{klass.table_name}.id asc").preload(:path)
|
793
|
+
|
794
|
+
# records[0].path.should == [grandpa] # grandpa
|
795
|
+
# records[1].path.should == [grandpa, dad] # dad
|
796
|
+
# records[2].path.should == [mom] # mom
|
797
|
+
# records[3].path.should == [grandpa, dad, mom, billy] # billy
|
798
|
+
# end
|
799
|
+
|
800
|
+
# it "should eager_load path in the correct order" do
|
801
|
+
# records = klass.order("#{klass.table_name}.id asc").eager_load(:path)
|
802
|
+
|
803
|
+
# records[0].path.should == [grandpa] # grandpa
|
804
|
+
# records[1].path.should == [grandpa, dad] # dad
|
805
|
+
# records[2].path.should == [mom] # mom
|
806
|
+
# records[3].path.should == [grandpa, dad, mom, billy] # billy
|
807
|
+
# end
|
808
|
+
|
809
|
+
# it "should include path in the correct order" do
|
810
|
+
# records = klass.order("#{klass.table_name}.id asc").includes(:path)
|
811
|
+
|
812
|
+
# records[0].path.should == [grandpa] # grandpa
|
813
|
+
# records[1].path.should == [grandpa, dad] # dad
|
814
|
+
# records[2].path.should == [mom] # mom
|
815
|
+
# records[3].path.should == [grandpa, dad, mom, billy] # billy
|
816
|
+
# end
|
817
|
+
# end
|
290
818
|
end
|
291
819
|
|
292
820
|
describe "models with separate link tables" do
|
293
|
-
|
294
|
-
@klass = SeparateLinkModel
|
295
|
-
end
|
821
|
+
let(:klass) { SeparateLinkModel }
|
296
822
|
|
297
823
|
it_should_behave_like "DAG Model"
|
298
824
|
end
|
299
825
|
|
300
826
|
describe "models with unified link tables" do
|
301
|
-
|
302
|
-
@klass = UnifiedLinkModel
|
303
|
-
end
|
827
|
+
let(:klass) { UnifiedLinkModel }
|
304
828
|
|
305
829
|
it_should_behave_like "DAG Model"
|
306
830
|
|
307
831
|
it "should create links that include the category type" do
|
308
|
-
record =
|
832
|
+
record = klass.create!
|
309
833
|
|
310
|
-
record.parent_links.first.category_type.
|
311
|
-
record.
|
834
|
+
expect(record.parent_links.first.category_type).to eq(klass.name)
|
835
|
+
expect(record.subtree_links.first.category_type).to eq(klass.name)
|
312
836
|
end
|
313
837
|
end
|
314
838
|
end
|