JohnSmall-acts-as-hausdorff-space 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +10 -0
- data/VERSION.yml +1 -1
- data/acts-as-hausdorff-space.gemspec +97 -0
- data/design_idea.rdoc +27 -0
- data/doc/LICENSE.html +113 -0
- data/doc/MindoroMarine.html +153 -0
- data/doc/MindoroMarine/Acts.html +150 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace.html +160 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace/ActMethods.html +230 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace/ClassMethods.html +466 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace/Gap.html +193 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace/HSArray.html +234 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace/InstanceMethods.html +1152 -0
- data/doc/MindoroMarine/Acts/HausdorffSpace/VirtualRoot.html +261 -0
- data/doc/README_rdoc.html +386 -0
- data/doc/created.rid +1 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +140 -0
- data/doc/js/darkfish.js +116 -0
- data/doc/js/jquery.js +32 -0
- data/doc/js/quicksearch.js +114 -0
- data/doc/js/thickbox-compressed.js +10 -0
- data/doc/lib/acts-as-hausdorff-space_rb.html +55 -0
- data/doc/lib/acts_as_hausdorff_space_rb.html +53 -0
- data/doc/rdoc.css +696 -0
- data/lib/acts_as_hausdorff_space.rb +22 -14
- data/test/acts_as_hausdorff_space_test.rb +13 -0
- metadata +48 -4
@@ -0,0 +1,261 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
3
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
|
7
|
+
|
8
|
+
<title>Class: MindoroMarine::Acts::HausdorffSpace::VirtualRoot</title>
|
9
|
+
|
10
|
+
<link rel="stylesheet" href="../../../rdoc.css" type="text/css" media="screen" />
|
11
|
+
|
12
|
+
<script src="../../../js/jquery.js" type="text/javascript"
|
13
|
+
charset="utf-8"></script>
|
14
|
+
<script src="../../../js/thickbox-compressed.js" type="text/javascript"
|
15
|
+
charset="utf-8"></script>
|
16
|
+
<script src="../../../js/quicksearch.js" type="text/javascript"
|
17
|
+
charset="utf-8"></script>
|
18
|
+
<script src="../../../js/darkfish.js" type="text/javascript"
|
19
|
+
charset="utf-8"></script>
|
20
|
+
|
21
|
+
</head>
|
22
|
+
<body class="class">
|
23
|
+
|
24
|
+
<div id="metadata">
|
25
|
+
<div id="file-metadata">
|
26
|
+
<div id="file-list-section" class="section">
|
27
|
+
<h3 class="section-header">In Files</h3>
|
28
|
+
<div class="section-body">
|
29
|
+
<ul>
|
30
|
+
|
31
|
+
<li><a href="../../../lib/acts_as_hausdorff_space.rb.html?TB_iframe=true&height=550&width=785"
|
32
|
+
class="thickbox" title="lib/acts_as_hausdorff_space.rb">lib/acts_as_hausdorff_space.rb</a></li>
|
33
|
+
|
34
|
+
</ul>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div id="class-metadata">
|
42
|
+
|
43
|
+
<!-- Parent Class -->
|
44
|
+
|
45
|
+
<div id="parent-class-section" class="section">
|
46
|
+
<h3 class="section-header">Parent</h3>
|
47
|
+
|
48
|
+
<p class="link">Object</p>
|
49
|
+
|
50
|
+
</div>
|
51
|
+
|
52
|
+
|
53
|
+
<!-- Namespace Contents -->
|
54
|
+
|
55
|
+
|
56
|
+
<!-- Method Quickref -->
|
57
|
+
|
58
|
+
<div id="method-list-section" class="section">
|
59
|
+
<h3 class="section-header">Methods</h3>
|
60
|
+
<ul class="link-list">
|
61
|
+
|
62
|
+
<li><a href="#M000001">::new</a></li>
|
63
|
+
|
64
|
+
</ul>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
|
68
|
+
<!-- Included Modules -->
|
69
|
+
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<div id="project-metadata">
|
73
|
+
|
74
|
+
|
75
|
+
<div id="fileindex-section" class="section project-section">
|
76
|
+
<h3 class="section-header">Files</h3>
|
77
|
+
<ul>
|
78
|
+
|
79
|
+
<li class="file"><a href="../../../LICENSE.html">LICENSE</a></li>
|
80
|
+
|
81
|
+
<li class="file"><a href="../../../README_rdoc.html">README.rdoc</a></li>
|
82
|
+
|
83
|
+
</ul>
|
84
|
+
</div>
|
85
|
+
|
86
|
+
|
87
|
+
<div id="classindex-section" class="section project-section">
|
88
|
+
<h3 class="section-header">Class Index
|
89
|
+
<span class="search-toggle"><img src="../../../images/find.png"
|
90
|
+
height="16" width="16" alt="[+]"
|
91
|
+
title="show/hide quicksearch" /></span></h3>
|
92
|
+
<form action="#" method="get" accept-charset="utf-8" class="initially-hidden">
|
93
|
+
<fieldset>
|
94
|
+
<legend>Quicksearch</legend>
|
95
|
+
<input type="text" name="quicksearch" value=""
|
96
|
+
class="quicksearch-field" />
|
97
|
+
</fieldset>
|
98
|
+
</form>
|
99
|
+
|
100
|
+
<ul class="link-list">
|
101
|
+
|
102
|
+
<li><a href="../../../MindoroMarine.html">MindoroMarine</a></li>
|
103
|
+
|
104
|
+
<li><a href="../../../MindoroMarine/Acts/HausdorffSpace/ActMethods.html">MindoroMarine::Acts::HausdorffSpace::ActMethods</a></li>
|
105
|
+
|
106
|
+
<li><a href="../../../MindoroMarine/Acts/HausdorffSpace/ClassMethods.html">MindoroMarine::Acts::HausdorffSpace::ClassMethods</a></li>
|
107
|
+
|
108
|
+
<li><a href="../../../MindoroMarine/Acts/HausdorffSpace/Gap.html">MindoroMarine::Acts::HausdorffSpace::Gap</a></li>
|
109
|
+
|
110
|
+
<li><a href="../../../MindoroMarine/Acts/HausdorffSpace/HSArray.html">MindoroMarine::Acts::HausdorffSpace::HSArray</a></li>
|
111
|
+
|
112
|
+
<li><a href="../../../MindoroMarine/Acts/HausdorffSpace/InstanceMethods.html">MindoroMarine::Acts::HausdorffSpace::InstanceMethods</a></li>
|
113
|
+
|
114
|
+
<li><a href="../../../MindoroMarine/Acts/HausdorffSpace/VirtualRoot.html">MindoroMarine::Acts::HausdorffSpace::VirtualRoot</a></li>
|
115
|
+
|
116
|
+
</ul>
|
117
|
+
<div id="no-class-search-results" style="display: none;">No matching classes.</div>
|
118
|
+
</div>
|
119
|
+
|
120
|
+
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
|
124
|
+
<div id="documentation">
|
125
|
+
<h1 class="class">MindoroMarine::Acts::HausdorffSpace::VirtualRoot</h1>
|
126
|
+
|
127
|
+
<div id="description">
|
128
|
+
<p>
|
129
|
+
An instance of <a href="VirtualRoot.html">VirtualRoot</a> is a hidden root
|
130
|
+
that “owns” the actual roots. It’s only here so we can
|
131
|
+
use the same code for real roots and children. Otherwise we have to write
|
132
|
+
special code for the top level parents. Note: This is very different from
|
133
|
+
the concept of virtual roots in better_nested_set which is the equivalent
|
134
|
+
of <Klass>.root.children to get the immediate children of a top level root
|
135
|
+
</p>
|
136
|
+
|
137
|
+
</div>
|
138
|
+
|
139
|
+
<!-- Constants -->
|
140
|
+
|
141
|
+
|
142
|
+
<!-- Attributes -->
|
143
|
+
|
144
|
+
<div id="attribute-method-details" class="method-section section">
|
145
|
+
<h3 class="section-header">Attributes</h3>
|
146
|
+
|
147
|
+
|
148
|
+
<div id="left-col-val-attribute-method" class="method-detail">
|
149
|
+
<a name="left_col_val"></a>
|
150
|
+
|
151
|
+
<a name="left_col_val="></a>
|
152
|
+
|
153
|
+
<div class="method-heading attribute-method-heading">
|
154
|
+
<span class="method-name">left_col_val</span><span
|
155
|
+
class="attribute-access-type">[RW]</span>
|
156
|
+
</div>
|
157
|
+
|
158
|
+
<div class="method-description">
|
159
|
+
|
160
|
+
<p class="missing-docs">(Not documented)</p>
|
161
|
+
|
162
|
+
</div>
|
163
|
+
</div>
|
164
|
+
|
165
|
+
<div id="right-col-val-attribute-method" class="method-detail">
|
166
|
+
<a name="right_col_val"></a>
|
167
|
+
|
168
|
+
<a name="right_col_val="></a>
|
169
|
+
|
170
|
+
<div class="method-heading attribute-method-heading">
|
171
|
+
<span class="method-name">right_col_val</span><span
|
172
|
+
class="attribute-access-type">[RW]</span>
|
173
|
+
</div>
|
174
|
+
|
175
|
+
<div class="method-description">
|
176
|
+
|
177
|
+
<p class="missing-docs">(Not documented)</p>
|
178
|
+
|
179
|
+
</div>
|
180
|
+
</div>
|
181
|
+
|
182
|
+
<div id="children-attribute-method" class="method-detail">
|
183
|
+
<a name="children"></a>
|
184
|
+
|
185
|
+
<div class="method-heading attribute-method-heading">
|
186
|
+
<span class="method-name">children</span><span
|
187
|
+
class="attribute-access-type">[R]</span>
|
188
|
+
</div>
|
189
|
+
|
190
|
+
<div class="method-description">
|
191
|
+
|
192
|
+
<p class="missing-docs">(Not documented)</p>
|
193
|
+
|
194
|
+
</div>
|
195
|
+
</div>
|
196
|
+
|
197
|
+
</div>
|
198
|
+
|
199
|
+
|
200
|
+
<!-- Methods -->
|
201
|
+
|
202
|
+
<div id="public-class-method-details" class="method-section section">
|
203
|
+
<h3 class="section-header">Public Class Methods</h3>
|
204
|
+
|
205
|
+
|
206
|
+
<div id="new-method" class="method-detail ">
|
207
|
+
<a name="M000001"></a>
|
208
|
+
|
209
|
+
<div class="method-heading">
|
210
|
+
|
211
|
+
<span class="method-name">new</span><span
|
212
|
+
class="method-args">( left_val,right_val)</span>
|
213
|
+
<span class="method-click-advice">click to toggle source</span>
|
214
|
+
|
215
|
+
</div>
|
216
|
+
|
217
|
+
<div class="method-description">
|
218
|
+
|
219
|
+
<p class="missing-docs">(Not documented)</p>
|
220
|
+
|
221
|
+
|
222
|
+
|
223
|
+
<div class="method-source-code"
|
224
|
+
id="new-source">
|
225
|
+
<pre>
|
226
|
+
<span class="ruby-comment cmt"># File lib/acts_as_hausdorff_space.rb, line 50</span>
|
227
|
+
<span class="ruby-keyword kw">def</span> <span class="ruby-identifier">initialize</span>( <span class="ruby-identifier">left_val</span>,<span class="ruby-identifier">right_val</span>)
|
228
|
+
<span class="ruby-keyword kw">self</span>.<span class="ruby-identifier">left_col_val</span> = <span class="ruby-identifier">left_val</span>
|
229
|
+
<span class="ruby-keyword kw">self</span>.<span class="ruby-identifier">right_col_val</span> = <span class="ruby-identifier">right_val</span>
|
230
|
+
<span class="ruby-ivar">@children</span> = <span class="ruby-constant">HSArray</span>.<span class="ruby-identifier">new</span>
|
231
|
+
<span class="ruby-ivar">@children</span>.<span class="ruby-identifier">parent</span> = <span class="ruby-keyword kw">self</span>
|
232
|
+
<span class="ruby-keyword kw">end</span></pre>
|
233
|
+
</div>
|
234
|
+
|
235
|
+
</div>
|
236
|
+
|
237
|
+
|
238
|
+
</div>
|
239
|
+
|
240
|
+
|
241
|
+
</div>
|
242
|
+
|
243
|
+
|
244
|
+
</div>
|
245
|
+
|
246
|
+
|
247
|
+
<div id="rdoc-debugging-section-dump" class="debugging-section">
|
248
|
+
|
249
|
+
<p>Disabled; run with --debug to generate this.</p>
|
250
|
+
|
251
|
+
</div>
|
252
|
+
|
253
|
+
<div id="validator-badges">
|
254
|
+
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
|
255
|
+
<p><small>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish
|
256
|
+
Rdoc Generator</a> 1.1.6</small>.</p>
|
257
|
+
</div>
|
258
|
+
|
259
|
+
</body>
|
260
|
+
</html>
|
261
|
+
|
@@ -0,0 +1,386 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
3
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
4
|
+
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
6
|
+
<head>
|
7
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
|
8
|
+
|
9
|
+
<title>File: README.rdoc [RDoc Documentation]</title>
|
10
|
+
|
11
|
+
<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet" />
|
12
|
+
|
13
|
+
<script src="./js/jquery.js" type="text/javascript"
|
14
|
+
charset="utf-8"></script>
|
15
|
+
<script src="./js/thickbox-compressed.js" type="text/javascript"
|
16
|
+
charset="utf-8"></script>
|
17
|
+
<script src="./js/quicksearch.js" type="text/javascript"
|
18
|
+
charset="utf-8"></script>
|
19
|
+
<script src="./js/darkfish.js" type="text/javascript"
|
20
|
+
charset="utf-8"></script>
|
21
|
+
</head>
|
22
|
+
|
23
|
+
<body class="file">
|
24
|
+
<div id="metadata">
|
25
|
+
<div id="project-metadata">
|
26
|
+
|
27
|
+
|
28
|
+
<div id="fileindex-section" class="section project-section">
|
29
|
+
<h3 class="section-header">Files</h3>
|
30
|
+
<ul>
|
31
|
+
|
32
|
+
<li class="file"><a href="./LICENSE.html">LICENSE</a></li>
|
33
|
+
|
34
|
+
<li class="file"><a href="./README_rdoc.html">README.rdoc</a></li>
|
35
|
+
|
36
|
+
</ul>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
|
40
|
+
<div id="classindex-section" class="section project-section">
|
41
|
+
<h3 class="section-header">Class Index
|
42
|
+
<span class="search-toggle"><img src="./images/find.png"
|
43
|
+
height="16" width="16" alt="[+]"
|
44
|
+
title="show/hide quicksearch" /></span></h3>
|
45
|
+
<form action="#" method="get" accept-charset="utf-8" class="initially-hidden">
|
46
|
+
<fieldset>
|
47
|
+
<legend>Quicksearch</legend>
|
48
|
+
<input type="text" name="quicksearch" value=""
|
49
|
+
class="quicksearch-field" />
|
50
|
+
</fieldset>
|
51
|
+
</form>
|
52
|
+
|
53
|
+
<ul class="link-list">
|
54
|
+
|
55
|
+
<li><a href="./MindoroMarine.html">MindoroMarine</a></li>
|
56
|
+
|
57
|
+
<li><a href="./MindoroMarine/Acts/HausdorffSpace/ActMethods.html">MindoroMarine::Acts::HausdorffSpace::ActMethods</a></li>
|
58
|
+
|
59
|
+
<li><a href="./MindoroMarine/Acts/HausdorffSpace/ClassMethods.html">MindoroMarine::Acts::HausdorffSpace::ClassMethods</a></li>
|
60
|
+
|
61
|
+
<li><a href="./MindoroMarine/Acts/HausdorffSpace/Gap.html">MindoroMarine::Acts::HausdorffSpace::Gap</a></li>
|
62
|
+
|
63
|
+
<li><a href="./MindoroMarine/Acts/HausdorffSpace/HSArray.html">MindoroMarine::Acts::HausdorffSpace::HSArray</a></li>
|
64
|
+
|
65
|
+
<li><a href="./MindoroMarine/Acts/HausdorffSpace/InstanceMethods.html">MindoroMarine::Acts::HausdorffSpace::InstanceMethods</a></li>
|
66
|
+
|
67
|
+
<li><a href="./MindoroMarine/Acts/HausdorffSpace/VirtualRoot.html">MindoroMarine::Acts::HausdorffSpace::VirtualRoot</a></li>
|
68
|
+
|
69
|
+
</ul>
|
70
|
+
<div id="no-class-search-results" style="display: none;">No matching classes.</div>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
|
77
|
+
<div id="documentation">
|
78
|
+
<p>
|
79
|
+
<h2>acts_as_hausdorff_space</h2>
|
80
|
+
</p>
|
81
|
+
<p>
|
82
|
+
acts_as_hausdorff_space is a gemified mixin for Rails ActiveRecord. It
|
83
|
+
implements the nested set model for maintaining recursive trees in a
|
84
|
+
database, but using real numbers rather than integers because real numbers
|
85
|
+
are a <a href="http://en.wikipedia.org/wiki/Hausdorff_space">Hausdorff
|
86
|
+
Space</a> and the integers aren’t
|
87
|
+
</p>
|
88
|
+
<p>
|
89
|
+
<h2> Installation </h2>
|
90
|
+
</p>
|
91
|
+
<ol>
|
92
|
+
<li>Make sure you have <a href="http://gems.github.com">gems.github.com</a> set
|
93
|
+
up in your gem sources
|
94
|
+
|
95
|
+
</li>
|
96
|
+
<li>sudo gem install acts-as-hausdorff-space
|
97
|
+
|
98
|
+
</li>
|
99
|
+
<li>In environment.rb add <tt> config.gem
|
100
|
+
"acts-as-hausdorff-space"</tt>
|
101
|
+
|
102
|
+
</li>
|
103
|
+
<li>Create a migration with left and right columns using the biggest decimal
|
104
|
+
representations available on your database.
|
105
|
+
|
106
|
+
</li>
|
107
|
+
<li>Add
|
108
|
+
|
109
|
+
<pre>
|
110
|
+
<tt> acts_as_hausdorff_space </tt>
|
111
|
+
</pre>
|
112
|
+
</li>
|
113
|
+
</ol>
|
114
|
+
<p>
|
115
|
+
to your model.
|
116
|
+
</p>
|
117
|
+
<ol>
|
118
|
+
<li>It’s not a drop in replacement for acts_as_nested_set so watch out.
|
119
|
+
|
120
|
+
</li>
|
121
|
+
</ol>
|
122
|
+
<p>
|
123
|
+
Other references
|
124
|
+
</p>
|
125
|
+
<ol>
|
126
|
+
<li>Source is at git@github.com:JohnSmall/acts-as-hausdorff-space.git
|
127
|
+
|
128
|
+
</li>
|
129
|
+
<li>Wiki is at
|
130
|
+
|
131
|
+
</li>
|
132
|
+
<li>Lighthouse bugtracking is at
|
133
|
+
|
134
|
+
</li>
|
135
|
+
<li>Google group for discussion is at <a
|
136
|
+
href="http://groups.google.com/group/acts_as_hausdorff_space">groups.google.com/group/acts_as_hausdorff_space</a>
|
137
|
+
|
138
|
+
</li>
|
139
|
+
<li>I’m at <a href="http://mindoro-marine.co.uk">John Small</a>
|
140
|
+
|
141
|
+
</li>
|
142
|
+
</ol>
|
143
|
+
<p>
|
144
|
+
<h2>A Problem with Nested Sets - Mega Peformance Issues</h2>
|
145
|
+
</p>
|
146
|
+
<p>
|
147
|
+
I was using the better_nested_set plugin in a territories model and trying
|
148
|
+
to load all the countries in the world and all the regions and sub-regions
|
149
|
+
and it was taking utterly ages. So I set about thinking what could be done
|
150
|
+
to improve performance. I’d written the code for nested sets before a
|
151
|
+
long time ago using Delphi & Interbase, so I knew the problems. But in
|
152
|
+
the meantime I’d learned some topology theory so I had a name and a
|
153
|
+
concept to put to what must have been starting everyone in the face .
|
154
|
+
Hausdorff Spaces! Integers aren’t a Hausdorff space but we’re
|
155
|
+
trying to implement a concept, namely nested sets, which are the defining
|
156
|
+
characteristic of a Hausdorff space, so we have to write extra code to
|
157
|
+
fudge integers to behave like they are a Hausdorff space and that is the
|
158
|
+
source of the performance hit. So the obvious thing to do is to use real
|
159
|
+
numbers which are a Hausdorff space and then the extra code and associated
|
160
|
+
performance hit melts away. After some fussing about version 0.1.0 is now
|
161
|
+
released. For small trees the overhead of using BigDecimal rather than
|
162
|
+
integers makes this method slower, but as the trees get bigger the relative
|
163
|
+
performance soon switches in favour of using BigDecimals. check the table
|
164
|
+
at the bottom to see how big the performance improvements can be.
|
165
|
+
</p>
|
166
|
+
<p>
|
167
|
+
<h2>A bit of history</h2>
|
168
|
+
</p>
|
169
|
+
<p>
|
170
|
+
Joe Celko, an SQL guru, popularized the idea of using nested sets for
|
171
|
+
databases back in his 1996 article reproduced <a
|
172
|
+
href="http://www.dbmsmag.com/9603d06.html">here</a>. Though the earliest
|
173
|
+
description is in <a href="http://www.kamfonas.com/id3.html">Kamfonas</a>.
|
174
|
+
Since it’s usually a bad idea to go against the advice of an SQL guru
|
175
|
+
every implementation I’ve seen follows his example in using integers
|
176
|
+
to set the nesting boundaries. So every implementation has had to deal with
|
177
|
+
the awkwardness that comes with using integers in a way they can’t
|
178
|
+
inherently be used. The description in <a
|
179
|
+
href="http://www.kamfonas.com/id3.html">Kamfonas</a> recomends using a
|
180
|
+
SKIP-VALUE to make sure you’ve got some space to put new entries in.
|
181
|
+
Though the code for that is obviously going to be complicated because
|
182
|
+
you’d need to work out a skip value for each node if you’re
|
183
|
+
adding a collection of nodes inside an already existing node. This is the
|
184
|
+
kind of coding fudge people have to do to make nested sets maintainable.
|
185
|
+
</p>
|
186
|
+
<p>
|
187
|
+
<h2>The Basic Idea</h2> This is copied from <a
|
188
|
+
href="http://threebit.net/tutorials/nestedset/tutorial1.html">ThreeBit</a>
|
189
|
+
and is the example used in better_nested_sets. The example uses integers.
|
190
|
+
</p>
|
191
|
+
<p>
|
192
|
+
An easy way to visualize how a nested set works is to think of a parent
|
193
|
+
entity surrounding all of its children, and its parent surrounding it, etc.
|
194
|
+
So this tree:
|
195
|
+
</p>
|
196
|
+
<pre>
|
197
|
+
root
|
198
|
+
|_ Child 1
|
199
|
+
|_ Child 1.1
|
200
|
+
|_ Child 1.2
|
201
|
+
|_ Child 2
|
202
|
+
|_ Child 2.1
|
203
|
+
|_ Child 2.2
|
204
|
+
</pre>
|
205
|
+
<p>
|
206
|
+
Could be visualized like this:
|
207
|
+
</p>
|
208
|
+
<pre>
|
209
|
+
___________________________________________________________________
|
210
|
+
| Root |
|
211
|
+
| ____________________________ ____________________________ |
|
212
|
+
| | Child 1 | | Child 2 | |
|
213
|
+
| | __________ _________ | | __________ _________ | |
|
214
|
+
| | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
|
215
|
+
1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
|
216
|
+
| |___________________________| |___________________________| |
|
217
|
+
|___________________________________________________________________|
|
218
|
+
</pre>
|
219
|
+
<p>
|
220
|
+
The numbers represent the left and right boundaries. The table then might
|
221
|
+
look like this:
|
222
|
+
</p>
|
223
|
+
<pre>
|
224
|
+
id | parent_id | lft | rgt | data
|
225
|
+
1 | | 1 | 14 | root
|
226
|
+
2 | 1 | 2 | 7 | Child 1
|
227
|
+
3 | 2 | 3 | 4 | Child 1.1
|
228
|
+
4 | 2 | 5 | 6 | Child 1.2
|
229
|
+
5 | 1 | 8 | 13 | Child 2
|
230
|
+
6 | 5 | 9 | 10 | Child 2.1
|
231
|
+
7 | 5 | 11 | 12 | Child 2.2
|
232
|
+
</pre>
|
233
|
+
<p>
|
234
|
+
To get all children of an entry <tt>parent</tt>, you
|
235
|
+
</p>
|
236
|
+
<pre>
|
237
|
+
SELECT * WHERE lft IS BETWEEN parent.lft AND parent.rgt
|
238
|
+
</pre>
|
239
|
+
<p>
|
240
|
+
To get the number of children, it’s
|
241
|
+
</p>
|
242
|
+
<pre>
|
243
|
+
(right - left - 1)/2
|
244
|
+
</pre>
|
245
|
+
<p>
|
246
|
+
To get a node and all its ancestors going back to the root, you
|
247
|
+
</p>
|
248
|
+
<pre>
|
249
|
+
SELECT * WHERE node.lft IS BETWEEN lft AND rgt
|
250
|
+
</pre>
|
251
|
+
<h4></h4>
|
252
|
+
<p>
|
253
|
+
Notes. Pretty obviously if you wanted to add a new child with C.1.1 as
|
254
|
+
parent you’ll have to update the entire tree from 4 onwards because
|
255
|
+
there is no integer between 3 and 4.
|
256
|
+
</p>
|
257
|
+
<p>
|
258
|
+
<h2>A bit of topological theory</h2>
|
259
|
+
</p>
|
260
|
+
<p>
|
261
|
+
There is no integer between 3 and 4, but there are an uncountable infinity
|
262
|
+
of real numbers between 3 and 4. That is the crux of the problem. Between
|
263
|
+
any two real numbers there is always another real number, no matter how
|
264
|
+
close they are. This property was first described by Archimedes and
|
265
|
+
it’s called the <a
|
266
|
+
href="http://en.wikipedia.org/wiki/Archimedean_property#Archimedean_property_of_the_real_numbers">Archimedean
|
267
|
+
property of real numbers</a>
|
268
|
+
</p>
|
269
|
+
<p>
|
270
|
+
What that means is that for real numbers we can surround any two of them
|
271
|
+
with disjoint open neighbourhoods and that is the defining property of a <a
|
272
|
+
href="http://en.wikipedia.org/wiki/Hausdorff_space">Hausdorff Space</a>.
|
273
|
+
That property of being able to put any two points inside disjoint sets of
|
274
|
+
nearby points means you can have infinite levels of nesting. The notion of
|
275
|
+
nested sets is inherently part of a Hausdorff space, but not of the
|
276
|
+
integers, which is why you have to write lots of awkward slow code to
|
277
|
+
maintain nested sets implemented with integers
|
278
|
+
</p>
|
279
|
+
<p>
|
280
|
+
<h2>The Implementation in Abstract</h2>
|
281
|
+
</p>
|
282
|
+
<p>
|
283
|
+
The implementation is the same as above, with two differences. I’m
|
284
|
+
not using parent_id because that defeats the main advantage of the nested
|
285
|
+
set model over the adjaceny list model and I’m using real numbers.
|
286
|
+
That means if we want to add a record between 3 and 4, we can make lft =
|
287
|
+
3.25 and rgt = 3.5, and we can keep on adding records to any depth or width
|
288
|
+
of tree we like, without having to update the rest of the tree.
|
289
|
+
</p>
|
290
|
+
<p>
|
291
|
+
<h2>The Real Life Implementation Constraints</h2>
|
292
|
+
</p>
|
293
|
+
<p>
|
294
|
+
Computers don’t use real numbers, they use binary arithmetic to
|
295
|
+
emulate real numbers. That means the lovely idea has to get ugly when it
|
296
|
+
meets the real world. There are maximum sizes for the real numbers used and
|
297
|
+
also minimum sizes depending on the real number precision limits. But
|
298
|
+
within those constraints we can implement the concept of nested sets using
|
299
|
+
real numbers fairly easily. We just have to keep our calculations away from
|
300
|
+
bumping up against the lower and upper limits. It means that there’s
|
301
|
+
going to be a limit to the number of children a parent node can own, and
|
302
|
+
also a limit to the depth of the tree. Most trees used in nested sets in
|
303
|
+
relational databases aren’t very deep, but they can be very wide.
|
304
|
+
When we add a new node into a gap in the tree we have to make sure it
|
305
|
+
leaves a gap between itself and its siblings or the parent lefts and
|
306
|
+
rights. That way we will always have some space to put a new record in the
|
307
|
+
gap, as long as the gap isn’t so small that we’re bumping up
|
308
|
+
against the limits of decimal precision. So when you set up your tables
|
309
|
+
always choose the maximum precision for that database.
|
310
|
+
</p>
|
311
|
+
<p>
|
312
|
+
The other issue when we add a new record, we want to leave space for more
|
313
|
+
records but we don’t know in advance how many records to leave space
|
314
|
+
for. We could do what’s done in the integer implementations of nested
|
315
|
+
sets, move every lft and rgt along a bit, but that is where the performance
|
316
|
+
hit comes from. So we have to leave gaps and fill them as best we can. If
|
317
|
+
each new node were to take up half of the remaining space between its
|
318
|
+
siblings and the boundaries of its parent, then we’d pretty soon bump
|
319
|
+
up against the decimal precision limit. To avoid that I’ve set things
|
320
|
+
up so that there’s a number called the bias, which is a division
|
321
|
+
factor. It works like this, I add a new child to a parent, I use the bias
|
322
|
+
to work out where to place the child inside what ever gap remains, roughly
|
323
|
+
(remaining gap)/bias. So the bigger the bias the less of the remaining gap
|
324
|
+
is taken up. The default bias is one million, to allow for wide and shallow
|
325
|
+
trees.
|
326
|
+
</p>
|
327
|
+
<p>
|
328
|
+
<h2> Relative Performance </h2>
|
329
|
+
</p>
|
330
|
+
<p>
|
331
|
+
For small trees integers are faster, for large trees
|
332
|
+
acts_as_hausdorff_space using BigDecimals are faster by a large margin. To
|
333
|
+
get a rough estimate of performance I wrote a test which adds 1000 children
|
334
|
+
to a root node, then goes through each child node adding children. This
|
335
|
+
really hammers the integer implementation of nested sets since every new
|
336
|
+
insertion requires updating every node to the right of the new insertion.
|
337
|
+
The full test description and updated results are <a
|
338
|
+
href="http://mindoro-marine.co.uk/nested_sets_tests">here</a>.
|
339
|
+
</p>
|
340
|
+
<p>
|
341
|
+
No. of children added is the number of child nodes added to each of the
|
342
|
+
1000 children of the root. aahs = acts_as_hausdorff_space, bns = using the
|
343
|
+
better_nested_set mixin.
|
344
|
+
</p>
|
345
|
+
<pre>
|
346
|
+
| SQLite3 | MySQL |
|
347
|
+
No of | aahs | bns | aahs | bns |
|
348
|
+
children
|
349
|
+
added
|
350
|
+
0 | 28 secs | 15 secs | 28 secs | 9 secs |
|
351
|
+
1 | 39 | 55 | 40 | 150 |
|
352
|
+
2 | 51 | 116 | 51 | 7,692 |
|
353
|
+
3 | 62 | 203 | 63 | 24,555 |
|
354
|
+
4 | 73 | 312 | 75 | I gave up |
|
355
|
+
5 | 84 | 444 | 87 | |
|
356
|
+
6 | 96 | 596 | 99 | |
|
357
|
+
7 | 109 | 765 | 113 | |
|
358
|
+
8 | 121 | 952 | 129 | |
|
359
|
+
9 | 131 | 1156 | 144 | |
|
360
|
+
</pre>
|
361
|
+
<p>
|
362
|
+
As you can see adding children into large trees using
|
363
|
+
acts_as_hausdorff_space (aahs) is nice and linear in the number of children
|
364
|
+
added. Using the integer method implemented by better_nested_set things are
|
365
|
+
much slower and on MySQL the time taken blows up quite quickly.
|
366
|
+
</p>
|
367
|
+
<p>
|
368
|
+
I’ll be testing Postgres and trying to work out why MySQL blows up so
|
369
|
+
badly.
|
370
|
+
</p>
|
371
|
+
<h2>Copyright</h2>
|
372
|
+
<p>
|
373
|
+
Copyright © 2009 John Small. See <a href="LICENSE.html">LICENSE</a>
|
374
|
+
for details.
|
375
|
+
</p>
|
376
|
+
|
377
|
+
</div>
|
378
|
+
|
379
|
+
<div id="validator-badges">
|
380
|
+
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
|
381
|
+
<p><small>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish
|
382
|
+
Rdoc Generator</a> 1.1.6</small>.</p>
|
383
|
+
</div>
|
384
|
+
</body>
|
385
|
+
</html>
|
386
|
+
|