acts_as_dag 1.2.2 → 1.2.3
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 +7 -0
- data/lib/acts_as_dag/acts_as_dag.rb +26 -19
- data/spec/acts_as_dag_spec.rb +48 -22
- data/spec/spec_helper.rb +1 -1
- metadata +12 -16
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3d531050423c7ca9be51544e54596e0310b96b2c
|
4
|
+
data.tar.gz: a0ec98ace714701dec05881502ffa834646d9fab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c618e157390becaede12d0065b237945852cb3d64f5e4400933c10748e64a32bb80b1ecddc7245545b8dacea0c52d996ad74206440001c371f114ec821c55e64
|
7
|
+
data.tar.gz: 59fb1e46422ff35ec92c13b3877be868ce044ce4c50011b3a55f992877bd4965c109076937eb92e5effbf9717803145f872a99d2495e4659c21773c2c19ea66a
|
@@ -75,7 +75,7 @@ module ActsAsDAG
|
|
75
75
|
after_create :initialize_descendants
|
76
76
|
|
77
77
|
extend ActsAsDAG::ClassMethods
|
78
|
-
include ActsAsDAG::InstanceMethods
|
78
|
+
include ActsAsDAG::InstanceMethods
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
@@ -88,7 +88,7 @@ module ActsAsDAG
|
|
88
88
|
# Can pass a list of categories and only those will be reorganized
|
89
89
|
def reorganize(categories_to_reorganize = self.all)
|
90
90
|
return if categories_to_reorganize.empty?
|
91
|
-
|
91
|
+
|
92
92
|
reset_hierarchy(categories_to_reorganize)
|
93
93
|
|
94
94
|
word_count_groups = categories_to_reorganize.group_by{|category| ActsAsDAG::HelperMethods.word_count(category)}.sort
|
@@ -101,16 +101,16 @@ module ActsAsDAG
|
|
101
101
|
|
102
102
|
# Try drop each category into each root
|
103
103
|
categories.sort_by(&:name).each do |category|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
104
|
+
ActiveRecord::Base.benchmark "Analyze #{category.name}" do
|
105
|
+
suitable_parent = false
|
106
|
+
roots_categories.each do |root|
|
107
|
+
suitable_parent = true if ActsAsDAG::HelperMethods.plinko(root, category)
|
108
|
+
end
|
109
|
+
unless suitable_parent
|
110
|
+
ActiveRecord::Base.logger.info { "Plinko couldn't find a suitable parent for #{category.name}" }
|
111
|
+
categories_with_no_parents << category
|
112
|
+
end
|
112
113
|
end
|
113
|
-
puts "took #{Time.now - start} to analyze #{category.name}"
|
114
114
|
end
|
115
115
|
|
116
116
|
# Add all categories from this group without suitable parents to the roots
|
@@ -136,7 +136,7 @@ module ActsAsDAG
|
|
136
136
|
category.send :initialize_links
|
137
137
|
category.send :initialize_descendants
|
138
138
|
end
|
139
|
-
end
|
139
|
+
end
|
140
140
|
end
|
141
141
|
|
142
142
|
module InstanceMethods
|
@@ -145,6 +145,13 @@ module ActsAsDAG
|
|
145
145
|
self.class.roots.exists? self
|
146
146
|
end
|
147
147
|
|
148
|
+
def make_root
|
149
|
+
ancestor_links.delete_all
|
150
|
+
parent_links.delete_all
|
151
|
+
send :initialize_links
|
152
|
+
send :initialize_descendants
|
153
|
+
end
|
154
|
+
|
148
155
|
# Adds a category as a parent of this category (self)
|
149
156
|
def add_parent(parent)
|
150
157
|
ActsAsDAG::HelperMethods.link(parent, self)
|
@@ -207,7 +214,7 @@ module ActsAsDAG
|
|
207
214
|
def self.plinko(current, other)
|
208
215
|
# ActiveRecord::Base.logger.info { "Plinkoing '#{other.name}' into '#{current.name}'..." }
|
209
216
|
if should_descend_from?(current, other)
|
210
|
-
# Find the descendants of the current category that +other+ should descend from
|
217
|
+
# Find the descendants of the current category that +other+ should descend from
|
211
218
|
descendants_other_should_descend_from = current.descendants.select{|descendant| should_descend_from?(descendant, other) }
|
212
219
|
# Of those, find the categories with the most number of matching words and make +other+ their child
|
213
220
|
# We find all suitable candidates to provide support for categories whose names are permutations of each other
|
@@ -219,7 +226,7 @@ module ActsAsDAG
|
|
219
226
|
other.add_parent(new_parent)
|
220
227
|
|
221
228
|
# We've just affected the associations in ways we can not possibly imagine, so let's clear the association cache
|
222
|
-
current.clear_association_cache
|
229
|
+
current.clear_association_cache
|
223
230
|
end
|
224
231
|
return true
|
225
232
|
end
|
@@ -235,8 +242,8 @@ module ActsAsDAG
|
|
235
242
|
unless plinko(current, category)
|
236
243
|
end
|
237
244
|
end
|
238
|
-
end
|
239
|
-
end
|
245
|
+
end
|
246
|
+
end
|
240
247
|
|
241
248
|
# Returns the portion of this category's name that is not present in any of it's parents
|
242
249
|
def self.unique_name_portion(current)
|
@@ -331,7 +338,7 @@ module ActsAsDAG
|
|
331
338
|
# A F
|
332
339
|
# / \ /
|
333
340
|
# B C
|
334
|
-
# |
|
341
|
+
# |
|
335
342
|
# | D
|
336
343
|
# \ /
|
337
344
|
# E
|
@@ -350,7 +357,7 @@ module ActsAsDAG
|
|
350
357
|
|
351
358
|
# Create a descendant link to iteself, then iterate through all children
|
352
359
|
# We add this node to the ancestor array we received
|
353
|
-
# Then we create a descendant link between it and all nodes in the array we were passed (nodes traversed between it and all its ancestors affected by the unlinking).
|
360
|
+
# Then we create a descendant link between it and all nodes in the array we were passed (nodes traversed between it and all its ancestors affected by the unlinking).
|
354
361
|
# Then iterate to all children of the current node passing the ancestor array along
|
355
362
|
def self.rebuild_descendant_links(current, ancestors = [])
|
356
363
|
indent = Array.new(ancestors.size, " ").join
|
@@ -392,4 +399,4 @@ module ActsAsDAG
|
|
392
399
|
|
393
400
|
validates_presence_of :ancestor_id, :descendant_id
|
394
401
|
end
|
395
|
-
end
|
402
|
+
end
|
data/spec/acts_as_dag_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe 'acts_as_dag' do
|
|
5
5
|
before(:each) do
|
6
6
|
@klass.destroy_all # Because we're using sqlite3 and it doesn't support transactional specs (afaik)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
describe "and" do
|
10
10
|
before(:each) do
|
11
11
|
@grandpa = @klass.create(:name => 'grandpa')
|
@@ -81,7 +81,7 @@ describe 'acts_as_dag' do
|
|
81
81
|
|
82
82
|
@grandpa.descendants.should == [@grandpa, @dad, @child]
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
it "should be able to test descent" do
|
86
86
|
@dad.add_child(@child)
|
87
87
|
@grandpa.add_child(@dad)
|
@@ -95,13 +95,40 @@ describe 'acts_as_dag' do
|
|
95
95
|
it "should be a root node immediately after saving" do
|
96
96
|
@grandpa.parents.should be_empty
|
97
97
|
@grandpa.root?.should be_true
|
98
|
-
end
|
98
|
+
end
|
99
99
|
|
100
100
|
it "should be a child if it has a parent" do
|
101
101
|
@grandpa.add_child(@dad)
|
102
102
|
@grandpa.add_child(@mom)
|
103
103
|
@klass.children.order(:id).should == [@dad, @mom]
|
104
|
-
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when a record hierarchy exists" do
|
108
|
+
before(:each) do
|
109
|
+
@grandma = @klass.create(:name => 'grandma')
|
110
|
+
@mom = @klass.create(:name => 'mom')
|
111
|
+
@brother = @klass.create(:name => 'brother')
|
112
|
+
|
113
|
+
@grandma.add_child(@mom)
|
114
|
+
@mom.add_child(@brother)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "destroying a record should delete the associated hierarchy-tracking records " do
|
118
|
+
@mom.destroy
|
119
|
+
@mom.descendant_links.should be_empty
|
120
|
+
@mom.ancestor_links.should be_empty
|
121
|
+
@mom.parent_links.should be_empty
|
122
|
+
@mom.child_links.should be_empty
|
123
|
+
end
|
124
|
+
|
125
|
+
it "make_root should make the record a root, but maintain it's children" do
|
126
|
+
@mom.make_root
|
127
|
+
|
128
|
+
@mom.should be_root
|
129
|
+
@mom.parents.should be_empty
|
130
|
+
@mom.children.should be_present
|
131
|
+
end
|
105
132
|
end
|
106
133
|
|
107
134
|
describe "reorganization" do
|
@@ -110,7 +137,7 @@ describe 'acts_as_dag' do
|
|
110
137
|
@totem_pole = @klass.create(:name => "totem pole")
|
111
138
|
@big_totem_pole = @klass.create(:name => "big totem pole")
|
112
139
|
@big_model_totem_pole = @klass.create(:name => "big model totem pole")
|
113
|
-
@big_red_model_totem_pole = @klass.create(:name => "big red model totem pole")
|
140
|
+
@big_red_model_totem_pole = @klass.create(:name => "big red model totem pole")
|
114
141
|
end
|
115
142
|
|
116
143
|
it "should reinitialize links and descendants after resetting the hierarchy" do
|
@@ -121,18 +148,18 @@ describe 'acts_as_dag' do
|
|
121
148
|
@big_totem_pole.descendants.should == [@big_totem_pole]
|
122
149
|
end
|
123
150
|
|
124
|
-
it "should be able to determine whether one category is an ancestor of the other by inspecting the name" do
|
151
|
+
it "should be able to determine whether one category is an ancestor of the other by inspecting the name" do
|
125
152
|
ActsAsDAG::HelperMethods.should_descend_from?(@totem_pole, @big_totem_pole).should be_true
|
126
153
|
ActsAsDAG::HelperMethods.should_descend_from?(@big_totem_pole, @totem_pole).should be_false
|
127
154
|
end
|
128
155
|
|
129
|
-
it "should be able to determine the number of matching words in two categories names" do
|
156
|
+
it "should be able to determine the number of matching words in two categories names" do
|
130
157
|
ActsAsDAG::HelperMethods.matching_word_count(@totem_pole, @big_totem_pole).should == 2
|
131
158
|
end
|
132
159
|
|
133
160
|
it "should arrange the categories correctly when not passed any arguments" do
|
134
161
|
@klass.reorganize
|
135
|
-
|
162
|
+
|
136
163
|
@totem.children.should == [@totem_pole]
|
137
164
|
@totem_pole.children.should == [@big_totem_pole]
|
138
165
|
@big_totem_pole.children.should == [@big_model_totem_pole]
|
@@ -141,7 +168,7 @@ describe 'acts_as_dag' do
|
|
141
168
|
|
142
169
|
it "should arrange the categories correctly when passed a set of nodes to reorganize" do
|
143
170
|
@klass.reorganize [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole]
|
144
|
-
|
171
|
+
|
145
172
|
@totem.reload.children.should == [@totem_pole]
|
146
173
|
@totem_pole.reload.children.should == [@big_totem_pole]
|
147
174
|
@big_totem_pole.reload.children.should == [@big_model_totem_pole]
|
@@ -158,7 +185,7 @@ describe 'acts_as_dag' do
|
|
158
185
|
@big_totem_pole.children.should == [@big_model_totem_pole]
|
159
186
|
@big_model_totem_pole.reload.children.should == [@big_red_model_totem_pole]
|
160
187
|
end
|
161
|
-
|
188
|
+
|
162
189
|
it "should still work when there are categories that are permutations of each other" do
|
163
190
|
@big_totem_pole_model = @klass.create(:name => "big totem pole model")
|
164
191
|
|
@@ -169,7 +196,7 @@ describe 'acts_as_dag' do
|
|
169
196
|
(@big_totem_pole.children - [@big_model_totem_pole, @big_totem_pole_model]).should == []
|
170
197
|
@big_model_totem_pole.reload.children.should == [@big_red_model_totem_pole]
|
171
198
|
@big_totem_pole_model.reload.children.should == [@big_red_model_totem_pole]
|
172
|
-
end
|
199
|
+
end
|
173
200
|
|
174
201
|
describe "when there is a single long inheritance chain" do
|
175
202
|
before(:each) do
|
@@ -195,14 +222,14 @@ describe 'acts_as_dag' do
|
|
195
222
|
end
|
196
223
|
|
197
224
|
it "should return multiple instances of descendants before breaking the old link" do
|
198
|
-
@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)
|
225
|
+
@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)
|
199
226
|
end
|
200
227
|
|
201
228
|
it "should return the correct inheritance chain after breaking the old link" do
|
202
229
|
@totem_pole.remove_child(@big_model_totem_pole)
|
203
230
|
|
204
|
-
@totem_pole.children.sort_by(&:id).should == [@big_totem_pole].sort_by(&:id)
|
205
|
-
@totem.descendants.sort_by(&:id).should == [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
231
|
+
@totem_pole.children.sort_by(&:id).should == [@big_totem_pole].sort_by(&:id)
|
232
|
+
@totem.descendants.sort_by(&:id).should == [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
206
233
|
end
|
207
234
|
|
208
235
|
it "should return the correct inheritance chain after breaking the old link when there is are two ancestor root nodes" do
|
@@ -215,7 +242,7 @@ describe 'acts_as_dag' do
|
|
215
242
|
@totem.descendants.sort_by(&:id).should == [@totem, @totem_pole, @big_totem_pole, @big_model_totem_pole, @big_red_model_totem_pole].sort_by(&:id)
|
216
243
|
end
|
217
244
|
end
|
218
|
-
end
|
245
|
+
end
|
219
246
|
end
|
220
247
|
|
221
248
|
describe "and two paths of the same length exist to the same node" do
|
@@ -229,14 +256,14 @@ describe 'acts_as_dag' do
|
|
229
256
|
@grandpa.add_child(@dad)
|
230
257
|
@dad.add_child(@child)
|
231
258
|
@child.add_parent(@mom)
|
232
|
-
@mom.add_parent(@grandpa)
|
259
|
+
@mom.add_parent(@grandpa)
|
233
260
|
end
|
234
261
|
|
235
262
|
it "descendants should not return multiple instances of a child" do
|
236
263
|
@grandpa.descendants.sort_by(&:id).should == [@grandpa, @dad, @mom, @child].sort_by(&:id)
|
237
|
-
end
|
264
|
+
end
|
238
265
|
|
239
|
-
describe "and a link between parent and ancestor is removed" do
|
266
|
+
describe "and a link between parent and ancestor is removed" do
|
240
267
|
before(:each) do
|
241
268
|
# the incest is undone!
|
242
269
|
@dad.remove_parent(@grandpa)
|
@@ -253,7 +280,7 @@ describe 'acts_as_dag' do
|
|
253
280
|
@mom.descendants.sort_by(&:id).should == [@mom, @child].sort_by(&:id)
|
254
281
|
@dad.descendants.sort_by(&:id).should == [@dad, @child].sort_by(&:id)
|
255
282
|
@grandpa.descendants.sort_by(&:id).should == [@grandpa, @mom, @child].sort_by(&:id)
|
256
|
-
end
|
283
|
+
end
|
257
284
|
end
|
258
285
|
end
|
259
286
|
end
|
@@ -269,7 +296,6 @@ describe 'acts_as_dag' do
|
|
269
296
|
describe "models with unified link tables" do
|
270
297
|
before(:each) do
|
271
298
|
@klass = UnifiedLinkModel
|
272
|
-
@klass.logger = Logger.new(STDOUT)
|
273
299
|
end
|
274
300
|
|
275
301
|
it_should_behave_like "DAG Model"
|
@@ -280,5 +306,5 @@ describe 'acts_as_dag' do
|
|
280
306
|
record.parent_links.first.category_type.should == @klass.name
|
281
307
|
record.descendant_links.first.category_type.should == @klass.name
|
282
308
|
end
|
283
|
-
end
|
284
|
-
end
|
309
|
+
end
|
310
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_dag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nicholas Jakobsen
|
@@ -10,22 +9,20 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2014-06-05 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activerecord
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- - ~>
|
18
|
+
- - "~>"
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '4.0'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- - ~>
|
25
|
+
- - "~>"
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '4.0'
|
31
28
|
description:
|
@@ -34,34 +31,33 @@ executables: []
|
|
34
31
|
extensions: []
|
35
32
|
extra_rdoc_files: []
|
36
33
|
files:
|
37
|
-
-
|
34
|
+
- LICENSE
|
35
|
+
- README.rdoc
|
38
36
|
- lib/acts_as_dag.rb
|
37
|
+
- lib/acts_as_dag/acts_as_dag.rb
|
39
38
|
- spec/acts_as_dag_spec.rb
|
40
39
|
- spec/spec_helper.rb
|
41
|
-
- LICENSE
|
42
|
-
- README.rdoc
|
43
40
|
homepage: http://github.com/rrn/acts_as_dag
|
44
41
|
licenses: []
|
42
|
+
metadata: {}
|
45
43
|
post_install_message:
|
46
44
|
rdoc_options: []
|
47
45
|
require_paths:
|
48
46
|
- lib
|
49
47
|
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
48
|
requirements:
|
52
|
-
- -
|
49
|
+
- - ">="
|
53
50
|
- !ruby/object:Gem::Version
|
54
51
|
version: '0'
|
55
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
53
|
requirements:
|
58
|
-
- -
|
54
|
+
- - ">="
|
59
55
|
- !ruby/object:Gem::Version
|
60
56
|
version: '0'
|
61
57
|
requirements: []
|
62
58
|
rubyforge_project:
|
63
|
-
rubygems_version:
|
59
|
+
rubygems_version: 2.2.2
|
64
60
|
signing_key:
|
65
|
-
specification_version:
|
61
|
+
specification_version: 4
|
66
62
|
summary: Adds directed acyclic graph functionality to ActiveRecord.
|
67
63
|
test_files: []
|