ActsAsFastNestedSet 0.0.1

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.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 / 2006-11-13
2
+
3
+ * First release
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/acts_as_fast_nested_set.rb
6
+ lib/counter_string.rb
7
+ test/test_acts_as_fast_nested_set.rb
data/README.txt ADDED
@@ -0,0 +1,48 @@
1
+ ActsAsFastNestedSet
2
+ by Adocca AB
3
+ http://www.adocca.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ Fast nested set implementation
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ None!
12
+
13
+ == SYNOPSYS:
14
+
15
+ Just add acts_as_fast_nested_set in your AR::Base descendant model class
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * Rails 1.1.6
20
+
21
+ == INSTALL:
22
+
23
+ * gem install acts_as_fast_nested_set
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2006 Adocca AB
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/counter_string.rb'
6
+ require './lib/acts_as_fast_nested_set.rb'
7
+
8
+ Hoe.new('ActsAsFastNestedSet', Adocca::Acts::ActsAsFastNestedSet::VERSION) do |p|
9
+ p.rubyforge_name = 'adocca_plugins'
10
+ p.summary = 'Fast nested set, really fast!'
11
+ # p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
12
+ # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ end
15
+
16
+ # vim: syntax=Ruby
@@ -0,0 +1,217 @@
1
+ require 'counter_string'
2
+ require 'active_record'
3
+
4
+ #
5
+ # Defines an act that behaves a bit like nested_set or tree, but is hopefully much faster.
6
+ #
7
+ # Like the nested_set it is much faster than tree when selecting subtrees, but unlike nested_set
8
+ # it wont have to rewrite the entire tree for each insertion or deletion.
9
+ #
10
+ # Each node is connected to a parent, and each node has a node_id.
11
+ #
12
+ # The node_id is created by concatenating a new identifier to the parents node_id, or just creating
13
+ # a brand new identifier if no parent is present, and appending a dot.
14
+ #
15
+ # An identifier is alphabetical, and the newest identifier is always alphabetically greater than
16
+ # the identifiers of its siblings.
17
+ #
18
+ # An example list of valid node_ids:
19
+ # AAAA. <- a top level node, with only one trailing dot
20
+ # AAAA.AAAA. <- a child of the node above
21
+ # AAAA.AAAB. <- sibling nr one
22
+ # AAAA.AAAB.AAAA. <- a child of sibling nr one
23
+ # AAAA.AAAC. <- sibling nr two
24
+ # AAAB. <- top level node two
25
+ # AAAC. <- top level node three
26
+ # AAAC.AAAA. <- child of top level node three
27
+ # AAAD. <- top level node four
28
+ #
29
+ # This structure makes it easy to find all children of a node:
30
+ # SELECT * FROM table WHERE node_id LIKE 'AAAA.%' <- selects top level node one and all its children
31
+ # SELECT * FROM table WHERE node_id LIKE 'AAAA.AAAB.%' <- selects sibling nr two and all its children
32
+ #
33
+ # Deleting is naturally equally easy.
34
+ #
35
+ # The tricky bit is generating node_ids - for that we use the counter_string, which increments like:
36
+ # "aaaa".next == "aaab"
37
+ # "ZZZZ".next == "ZZZa"
38
+ # "zzzz".next == "zzzzAAAA"
39
+ #
40
+ # This keeps the relation between node_ids within the tree intact, as well as provides a big (140000 entries)
41
+ # namespace for each level of the tree using only 5 chars in the node_id column, and providing an even bigger
42
+ # (basically unlimited) namespace with more than 5 chars in the node_id column (it will provide 140000 more entries
43
+ # for each extra 4 chars after the first 5)
44
+ #
45
+ # If you want to have several trees with different sets of node_ids (for example, several comment-trees connected
46
+ # to different models in the system), you can add :uniqueness_scope as a param to the acts_as_fast_nested_set
47
+ # method.
48
+ #
49
+ # Example:
50
+ # acts_as_fast_nested_set :uniqueness_scope => [:forum_id, :forum_class] <- Will have a separate tree of node_ids
51
+ # for each unique combination of forum_id
52
+ # and forum_class in the model.
53
+ #
54
+ module Adocca
55
+ module Acts
56
+ module ActsAsFastNestedSet
57
+ VERSION = "0.0.1"
58
+
59
+ def self.append_features(base)
60
+ super
61
+ base.extend(ClassMethods)
62
+ end
63
+
64
+ module ClassMethods
65
+ #
66
+ # Makes this model act as a fast_nested_set. It needs to
67
+ # have parent_id, node_id and naturally id among its attributes.
68
+ #
69
+ # If you want to create different trees (with colliding node_ids, use
70
+ # the :uniqueness_scope param.
71
+ #
72
+ # Example:
73
+ # acts_as_fast_nested_set :uniqueness_scope => [:forum_id, :forum_class] <- Will have a separate tree of node_ids
74
+ # for each unique combination of forum_id
75
+ # and forum_class in the model.
76
+ #
77
+ def acts_as_fast_nested_set(options = {})
78
+ belongs_to :parent, :class_name => self.name, :foreign_key => 'parent_id'
79
+ has_many :children, :class_name => self.name, :foreign_key => 'parent_id'
80
+ attr_protected :node_id, :parent_id
81
+ if options[:uniqueness_scope]
82
+ validates_uniqueness_of :node_id, :scope => options[:uniqueness_scope]
83
+ else
84
+ validates_uniqueness_of :node_id
85
+ end
86
+ validate :ensure_node_id
87
+ class_eval do
88
+ extend Adocca::Acts::ActsAsFastNestedSet::SingletonMethods
89
+ end
90
+ set_options(options)
91
+ include Adocca::Acts::ActsAsFastNestedSet::InstanceMethods
92
+ end
93
+ end
94
+ module SingletonMethods
95
+ def set_options(options)
96
+ @@options = options
97
+ end
98
+ def get_options
99
+ @@options
100
+ end
101
+ end
102
+ module InstanceMethods
103
+ #
104
+ # Returns the entire thread
105
+ #
106
+ def kin
107
+ self.class.find(:all, :conditions => ["node_id LIKE ?#{unique_with_and}",
108
+ "#{root_id}.%"] +
109
+ unique_params)
110
+ end
111
+ #
112
+ # Destroys all children and their children etc
113
+ #
114
+ def destroy_descendants
115
+ self.class.connection.delete(
116
+ self.class.sanitizeSQL(
117
+ ["DELETE FROM comments WHERE node_id LIKE ? AND id != ?#{unique_with_and}",
118
+ "#{self.node_id}%", self.id] + unique_params))
119
+ end
120
+ #
121
+ # All nodes with the same parent
122
+ #
123
+ def siblings
124
+ self.class.find_all_by_parent_id(self.parent_id)
125
+ end
126
+ #
127
+ # All our children and their children etc
128
+ #
129
+ def descendants
130
+ self.class.find(:all,
131
+ :conditions => ["node_id LIKE ? AND id != ?#{unique_with_and}",
132
+ "#{self.node_id}%", self.id] + unique_params)
133
+ end
134
+
135
+ def level
136
+ if self.node_id.nil?
137
+ 1
138
+ else
139
+ self.node_id.count "."
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def unique_statement
146
+ self.class.get_options[:uniqueness_scope].nil? ? "" : "#{self.class.get_options[:uniqueness_scope].join(" = ? AND ")} = ?"
147
+ end
148
+ def unique_with_where
149
+ " WHERE #{unique_statement}"
150
+ end
151
+ def unique_with_and
152
+ " AND #{unique_statement}"
153
+ end
154
+
155
+ def unique_params
156
+ self.class.get_options[:uniqueness_scope].map do |att| self.send(att) end
157
+ end
158
+
159
+ def ensure_node_id
160
+ if self.node_id.nil? || self.node_id.empty?
161
+ if self.parent
162
+ self.node_id = "#{self.parent.node_id}#{next_node_id(self.parent.node_id)}."
163
+ else
164
+ self.node_id = "#{next_node_id}."
165
+ end
166
+ end
167
+ true
168
+ end
169
+
170
+ def root_id
171
+ if self.node_id.nil?
172
+ nil
173
+ else
174
+ if (match = self.node_id.match(/^([[:alpha:]]+)\..*$/))
175
+ match[1]
176
+ else
177
+ raise "My node_id '#{self.node_id}' is illegal!"
178
+ end
179
+ end
180
+ end
181
+
182
+ def next_node_id(parentNodeId = nil)
183
+ if parentNodeId.nil?
184
+ maxId = self.class.connection.select_value(self.class.sanitizeSQL(["SELECT MAX(node_id) FROM comments#{unique_with_where}"] + unique_params))
185
+ if maxId.nil? # there are no other root nodes
186
+ "AAAA"
187
+ else # there are other root nodes
188
+ if (match = maxId.match(/^([[:alpha:]]+)\..*$/)) # the latest root-node is properly formed
189
+ CounterString.new(match[1]).next
190
+ else # its not properly formed
191
+ raise "node_id '#{maxId}' is not a legal node_id!"
192
+ end
193
+ end
194
+ else # this is not a root node
195
+ maxId = self.class.connection.select_value(self.class.sanitizeSQL(["SELECT MAX(node_id) FROM comments WHERE node_id LIKE ?#{unique_with_and}", "#{parentNodeId}%"] + unique_params))
196
+ if maxId.nil? # there are no nodes like the parent
197
+ raise "Cant find parent, no node with id '#{parentNodeId}'!"
198
+ else # there are nodes like the parent
199
+ restPart = maxId[(parentNodeId.size)..-1]
200
+ if restPart.empty? # but no children of the parent
201
+ "AAAA"
202
+ else # there are children of the parent
203
+ if (match = restPart.match(/^([[:alpha:]]+)\..*$/)) # and they are properly formed
204
+ CounterString.new(match[1]).next
205
+ else # but not properly formed
206
+ raise "node_id '#{maxId}' is not a legal descendant of '#{parentNodeId}'!"
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end # of def next_node_id
212
+ end # of module InstanceMethods
213
+ end
214
+ end
215
+ end
216
+
217
+ ActiveRecord::Base.send(:include, Adocca::Acts::ActsAsFastNestedSet)
@@ -0,0 +1,27 @@
1
+ class CounterString < String
2
+
3
+ def next
4
+ (self.size - 1).downto(0) do |index|
5
+ this_ord = self[index]
6
+ if new_ord = next_ord(this_ord)
7
+ return CounterString.new("#{self[0...index]}#{new_ord.chr}".ljust(self.size, "a"))
8
+ end
9
+ end
10
+ CounterString.new(self.ljust(self.size + 4, "a"))
11
+ end
12
+
13
+ private
14
+
15
+ def next_ord(ord)
16
+ if ord < 90
17
+ ord + 1
18
+ elsif ord == 90
19
+ 97
20
+ elsif ord < 122
21
+ ord + 1
22
+ else
23
+ false
24
+ end
25
+ end
26
+
27
+ end
File without changes
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: ActsAsFastNestedSet
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-11-13 00:00:00 +01:00
8
+ summary: Fast nested set, really fast!
9
+ require_paths:
10
+ - lib
11
+ email: ryand-ruby@zenspider.com
12
+ homepage: http://www.zenspider.com/ZSS/Products/ActsAsFastNestedSet/
13
+ rubyforge_project: adocca_plugins
14
+ description: Ryan Davis is too lazy to write a description
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Ryan Davis
31
+ files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ - Rakefile
36
+ - lib/acts_as_fast_nested_set.rb
37
+ - lib/counter_string.rb
38
+ - test/test_acts_as_fast_nested_set.rb
39
+ test_files:
40
+ - test/test_acts_as_fast_nested_set.rb
41
+ rdoc_options: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ executables: []
46
+
47
+ extensions: []
48
+
49
+ requirements: []
50
+
51
+ dependencies:
52
+ - !ruby/object:Gem::Dependency
53
+ name: hoe
54
+ version_requirement:
55
+ version_requirements: !ruby/object:Gem::Version::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 1.1.4
60
+ version: